• Welcome to Freedom Reborn Archive.
 

Updated Attribute: Driver

Started by Mystik, July 19, 2007, 03:41:43 PM

Previous topic - Next topic

Epimethee

Didn't have the time to complete it; RL got in the way, sorry... I also got a bit ambitious. :P

I'll try to see if I can make progress in the next few days

Mystik


lmalonsof

I'm very interested in this and I'd help you if I could... but I know nothing about python. Eventhough I'll try the script to see what happens.

Epimethee

Sorry for taking this long. I was stalled by some frustrating bugs. I should have a bit of time this weekend, so I'll get back to work. If the extras features are still buggy, I'll just remove them.

Epimethee

Not tested nearly enough, but solved my bug (which were two; same effect, unrelated causes)! Hopefully, it works correctly.  :D

This replaces the previous version of Driver/Vehicle, fixes bugs and adds new bugs, err... I mean, customization options. What's new:

Additional FFX_DRIVER_CUSTOM parameter: interface method
FFX_DRIVER_CUSTOM=[
["default","cat jalopy","","","beside"],
["types","UVehicle 1","UVehicle 2","UVehicle 3","AMethod"],
]
"beside": vehicle appears next to driver
"inside": vehicle replaces driver
"away": vehicle is away and will need to be called; if the vehicle can fly, it will come by air
"spawn": vehicle pops out of thin air when called
You'll need to update your ffxcustom2.py file manually to replace the existing default with the new version; if you already have entered customizations, merge them and add the new interface method. Ex:
FFX_DRIVER_CUSTOM=[
["default","cat jalopy","","","beside"],
["batman","batwing","","","away"],
["types","UVehicle 1","UVehicle 2","UVehicle 3","AMethod"],
]



New FFX_VEHICLE_CUSTOM:
Cargo mass determines the max number of passengers to allow in addition to the driver
FFX_VEHICLE_CUSTOM=[
["default", "500"],
["types", "ACargo (lbs)"],
]

You'll need to update your ffxcustom2.py file manually to add the above code.

In strings.txt, you'll need to add:
CUSTOM_SUMMONVEHICLE_01, call vehicle
CUSTOM_SUMMONVEHICLE_DESC_01, bring your vehicle near you
CUSTOM_UNSUMMONVEHICLE_01, send away
CUSTOM_UNSUMMONVEHICLE_DESC_01, send back your vehicle from where it came
CUSTOM_PASSENGERINVEHICLE_01, enter vehicle
CUSTOM_PASSENGERINVEHICLE_DESC_01, hitch a ride


In ffx.py, under Driver/Vehicle (the working version just above Spellcaster), replace the existing code by the following:
[spoiler]###################### vehicle and driver ##########################
# Dr. Mike's code, with some bugfixes and tweaks by Ep. inspired by Thor Reborn.
# Updated FFX 3.3 alpha
# status: 2.0
# TODO: M25 AI character code (yeah, right).

### Driver / Vehicle customization updated to include more options; this breaks the version in FFX 3.2 - using fallback code for this, though.
# Valid interface method names are "beside", "inside", "spawn" and "away"; set the default to "beside"
# contrarily to Dr Mike's intention, since his Driver demo character CatLaser also has the Utility Belt attribute.
# beside: vehicle appears next to driver
# inside: vehicle replaces driver
# away: vehicle is away and will need to be called
# spawn: vehicle pops out of thin air when called

def initvehicle(char,update=0):
    #remove a portrait if there is one
    FFX_AdjustPortrait(char,0)
    Object_SetAttr(char,'heroPoints', 0) #Ep.: I suppose the idea with this line was to prevent issues with "destroyed" vehicles

def initdriver(char,update=0):
    #find their vehicle
    template=getVehicle(char)
    name=getFormName()
    pos=Get_ObjectPos(char)
    try: #for compatibility with the shorter list format in FFX 3.2
        method = getByTemplate(char, FFX_DRIVER_CUSTOM, 4)
    except:
        method = 'beside'
        print "Driver attribute non-fatal error:"
        print "    '%' is not a valid interface method name, Please update yout FFX_DRIVER_CUSTOM configuration." % method
    if method == 'inside':
        RegTimer('OnSetVehicleInside', 0.5, 0, name)
    elif method == 'beside':
        pos = (pos[0]+30, pos[1]+30, pos[2])
    elif method == 'away':
        RegTimer('OnSetVehicleAway', 2.5, 0, name) #increased delay is necessary to get the vehicle to move all the way
    elif method == 'spawn':
        RegTimer('OnSetVehicleSpawn', 0.5, 0, name)
    else:
        Mission_StatusText('driver attribute error: invalid method %s' % method)
        print 'driver attribute fatal error:'
        print "    '%' is not a valid interface method name" % method
        return
    cshelper.spawn(name, template, pos)
    if (method == 'away') or (method == 'spawn'):
        #vehicle invisibility should kick in immediately
        Object_SetSecondaryState(name, SCSTATE_INVISIBLE, 10000, 0)
    alignWith(name, char)
    Mission_CustomAction('CUSTOM_ENTERVEHICLE',char,name,'OnEnterVehicle',5,0)
    Mission_CustomAction('CUSTOM_EXITVEHICLE',name,name,'OnExitVehicle',5,0)
    Object_SetVar(name,'vehicle_getDriver',char)
    AIDisable(name)


#Ep. 2007-08-16: adds a delay to make OnEnterVehicle() work
def OnSetVehicleInside(event):
    target = event.object
    char = Object_GetVar(target, 'vehicle_getDriver')
    OnEnterVehicle(target, char)


def OnSetVehicleAway(event):
    vehicle = event.object
    driver = Object_GetVar(vehicle, 'vehicle_getDriver')
    Object_SetVar(driver, 'driver_getVehicle', vehicle)
    #vehicle should be invisible when first sent away
    Object_SetSecondaryState(vehicle, SCSTATE_DISPLACE_IMAGE, 10000, 0)
    OnUnsummonVehicleAway(vehicle, driver)

def OnSummonVehicleAway(driver, dummy):
    vehicle = Object_GetVar(driver, 'driver_getVehicle')
    #make the vehicle reappear if needed
    Object_SetSecondaryState(vehicle, SCSTATE_DISPLACE_IMAGE, 0, REMOVE_STATE)
    Object_SetSecondaryState(vehicle, SCSTATE_INVISIBLE, 0, REMOVE_STATE)
    #recall the vehicle
    pos = Get_ObjectPos(driver)
    movementType = m25ai.AIDetermineMoveType(vehicle, "", 10000) #flying vehicles should fly
    Trigger_Move(vehicle, movementType, (pos[0]+30, pos[1]+30, pos[2]+20))
    Mission_CustomAction('CUSTOM_UNSUMMONVEHICLE', driver, vehicle, 'OnUnsummonVehicleAway', 50, 1)

def OnUnsummonVehicleAway(vehicle, driver):
    #make the vehicle go away
    movementType = m25ai.AIDetermineMoveType(vehicle, "", 10000)
    Trigger_Move(vehicle, movementType, getNearestMapSide(vehicle))
    Mission_CustomAction('CUSTOM_SUMMONVEHICLE', driver, driver, 'OnSummonVehicleAway', 1, 1)


def OnSetVehicleSpawn(event):
    vehicle = event.object
    driver = Object_GetVar(vehicle, 'vehicle_getDriver')
    Object_SetVar(driver, 'driver_getVehicle', vehicle)
    OnUnsummonVehicleSpawn(vehicle, driver)

def OnSummonVehicleSpawn(driver, dummy):
    vehicle = Object_GetVar(driver, 'driver_getVehicle')
    pos = Get_ObjectPos(driver)
    pos = (pos[0]+30, pos[1]+30, pos[2])
    FFX_Teleport(vehicle, pos)
    alignWith(vehicle, driver)
    #make the vehicle reappear
    Object_SetPrimaryState(vehicle, PCSTATE_EXILE, 0, REMOVE_STATE)
    Object_SetSecondaryState(vehicle, SCSTATE_INVISIBLE, 0, REMOVE_STATE)
    Mission_CustomAction('CUSTOM_UNSUMMONVEHICLE', driver, vehicle, 'OnUnsummonVehicleSpawn', 5, 1)

def OnUnsummonVehicleSpawn(vehicle, driver):
    #make the vehicle disappear
    Object_SetPrimaryState(vehicle, PCSTATE_EXILE, 10000, 0)
    Object_SetSecondaryState(vehicle, SCSTATE_INVISIBLE, 10000, 0)
    Mission_CustomAction('CUSTOM_SUMMONVEHICLE', driver, driver, 'OnSummonVehicleSpawn', 1, 1)

 
def OnEnterVehicle(vehicle, driver):
    #make the driver disappear
    Object_SetPrimaryState(driver, PCSTATE_EXILE, 10000, 0)
    Object_SetSecondaryState(driver, SCSTATE_INVISIBLE, 10000, 0)
    FFX_AdjustPortrait(driver, 0)
    RegTimer('OnEnterVehicle2', 2, 0, vehicle) #Increased delay to reduce risk of losing control of character
    regDeath(vehicle, 'OnVehicleDie')
    #for passengers; would need to be updated if Driver ever gets M25 AI
    maxMass = float( getByTemplate(vehicle, FFX_VEHICLE_CUSTOM, 1) ) / 2.2
    FFX_ObjectSetAttr(vehicle, 'vehiclePassengerMass', 0)
    for hero in GetAllHeroes():
        if hero not in (driver, vehicle):
            if Object_GetAttr(hero, 'mass') <= maxMass:
                Mission_CustomAction('CUSTOM_PASSENGERINVEHICLE', hero, vehicle, 'OnPassengerEnterVehicle', 5, 0)

#used for both entering (where target is the vehicle) and exiting (where it's the driver)
def OnEnterVehicle2(event):
    target=event.object
    FFX_AdjustPortrait(target,1)
    Mission_SelectHero(target)
    AIEnable(target)


def OnPassengerEnterVehicle(vehicle, passenger):
    if not FFX_HasPortrait(vehicle):
        Mission_StatusText("Wait for the driver!")
        return
    maxMass = float( getByTemplate(vehicle, FFX_VEHICLE_CUSTOM, 1) ) / 2.2
    currentMass = FFX_ObjectGetAttr(vehicle, 'vehiclePassengerMass')
    passengerMass = Object_GetAttr(passenger, 'mass')
    if (currentMass + passengerMass) > maxMass:
        Mission_StatusText("Not enough place left in vehicle!")
        Mission_StatusText("Left: %i  you: %i" % ( (maxMass-currentMass)*2.2, passengerMass*2.2 ))
        return
    FFX_ObjectGetAttr(vehicle, 'vehiclePassengerMass', currentMass + passengerMass)
    #make the passenger disappear
    Object_SetPrimaryState(passenger, PCSTATE_EXILE, 10000, 0)
    Object_SetSecondaryState(passenger, SCSTATE_INVISIBLE, 10000, 0)
    FFX_AdjustPortrait(passenger, 0)
    currentPassengers = missionobjvar.Object_GetVar(vehicle, 'vehicle_getPassengers', {'passenger1': '', 'passenger2': '', 'passenger3': '', })
    nbr = 0
    #simulates an ordered dictionary
    while nbr < 4:
        nbr = nbr + 1
        k = 'passenger%i'%nbr
        if currentPassengers[k] == '':
            currentPassengers[k] = passenger
            break
    missionobjvar.Object_SetVar(vehicle, 'vehicle_getPassengers', currentPassengers)
    Mission_CustomAction('drop %s' % getCharDisplayNameFromDAT(passenger), vehicle, vehicle, 'OnPassenger%iExitVehicle'%nbr, 1, 1)

def OnPassenger1ExitVehicle(vehicle, dummy):
    OnPassengerXExitVehicle(vehicle, 1)

def OnPassenger2ExitVehicle(vehicle, dummy):
    OnPassengerXExitVehicle(vehicle, 2)

def OnPassenger3ExitVehicle(vehicle, dummy):
    OnPassengerXExitVehicle(vehicle, 3)

# find the passenger associated with the custom command and remove it for the dictionary
def OnPassengerXExitVehicle(vehicle, nbr):
    currentPassengers = missionobjvar.Object_GetVar(vehicle, 'vehicle_getPassengers')
    passenger = currentPassengers['passenger%i'%nbr]
    currentPassengers['passenger%i'%nbr] = ''
    missionobjvar.Object_SetVar(vehicle, 'vehicle_getPassengers', currentPassengers)
    OnPassengerExitVehicle(vehicle, passenger)

def OnPassengerExitVehicle(vehicle, passenger):
    currentMass = FFX_ObjectGetAttr(vehicle, 'vehiclePassengerMass')
    passengerMass = Object_GetAttr(passenger, 'mass')
    FFX_ObjectGetAttr(vehicle, 'vehiclePassengerMass', currentMass - passengerMass)
    pos = Get_ObjectPos(vehicle)
    posOffset = (-50, -40, -30, 30, 40, 50)
    pos = (pos[0] + posOffset[randint(0,5)], pos[1] + posOffset[randint(0,3)], pos[2])
    FFX_Teleport(passenger, pos)
    alignWith(passenger, vehicle)
    Object_SetPrimaryState(passenger, PCSTATE_EXILE,0,REMOVE_STATE)
    Object_SetSecondaryState(passenger, SCSTATE_INVISIBLE,0,REMOVE_STATE)
    RegTimer('OnEnterVehicle2', 0.5, 0, passenger)


def OnVehicleDie(event):
    vehicle=event.object
    driver=Object_GetVar(vehicle,'vehicle_getDriver')
    Mission_RemoveCustomAction('CUSTOM_ENTERVEHICLE',driver,vehicle)
    RegTimer('OnVehicleDie2', 0.2, 0, vehicle)
    if FFX_HasPortrait(vehicle):
        OnExitVehicle('',vehicle)

def OnVehicleDie2(event):
    vehicle=event.object
    Trigger_Explosion(vehicle, 'ffx_ex_fire_small')
    Object_PlayEffect(vehicle, 'effect_buildingfire', '', 0, 0, 'centre')


def OnExitVehicle(dummy,vehicle):
    #figure out who our driver is...
    driver = Object_GetVar(vehicle, 'vehicle_getDriver')
    pos = Get_ObjectPos(vehicle)
    pos = (pos[0]+30, pos[1]+30, pos[2])
    FFX_Teleport(driver, pos)
    alignWith(driver, vehicle)
    Object_SetPrimaryState(driver, PCSTATE_EXILE,0,REMOVE_STATE)
    Object_SetSecondaryState(driver, SCSTATE_INVISIBLE,0,REMOVE_STATE)
    FFX_AdjustPortrait(vehicle, 0)
    AIDisable(vehicle)
    RegTimer('OnEnterVehicle2', 0.5, 0, driver)
    #drop all passengers still in vehicle
    currentPassengers = missionobjvar.Object_GetVar(vehicle, 'vehicle_getPassengers', {'passenger1': '', 'passenger2': '', 'passenger3': '', })
    for k in currentPassengers.keys():
        if currentPassengers[k] != '':
            OnPassengerExitVehicle(vehicle, currentPassengers[k])
            currentPassengers[k] = ''
    missionobjvar.Object_SetVar(vehicle, 'vehicle_getPassengers', currentPassengers)
    #remove custom commands from former and would-be passengers - doesn't work?
    for hero in GetAllHeroes():
        Mission_RemoveCustomAction('CUSTOM_PASSENGERINVEHICLE', hero, vehicle)
    for hero in getAllDisabledCharacters(): #not really the list of heroes (disabled don't have portraits), but rather of every disabled char
        Mission_RemoveCustomAction('CUSTOM_PASSENGERINVEHICLE', hero, vehicle) # repeats the for hero in GetAllHeroes() above in case of timing issues
        Mission_RemoveCustomAction('drop %s' % getCharDisplayNameFromDAT(hero), vehicle, vehicle)
    try: #for compatibility with the shorter list format in FFX 3.2
        method = getByTemplate(driver, FFX_DRIVER_CUSTOM, 4)
    except:
        method = 'beside'
    if method == 'spawn':
        Mission_CustomAction('CUSTOM_SUMMONVEHICLE', driver, driver, 'OnSummonVehicleSpawn', 1, 1)
    elif method == 'away':
        Mission_CustomAction('CUSTOM_SUMMONVEHICLE', driver, driver, 'OnSummonVehicleAway', 1, 1)


#Ep. 2006-03-31: Added optional randomisation, using the unused list entries. Inspired by a request by Benton Grey.
def getVehicle(char):
    templates = []
    for i in (1, 2, 3):
        temp = getByTemplate(char, FFX_DRIVER_CUSTOM, i)
        if temp != '':
            templates.append(temp)
    return templates[ randint(0, len(templates)-1) ]


[/spoiler]

Still in ffx.py, you'll need to add the following three utility functions:


# Returns the nearest (in 2D) corner of the map object; this point might or might not be reachable.
# (Note that the SkXMapInfo calipers info isn't used here, as it isn't reliable for interior maps.)
# Z-Axis value is returned, but it shouldn't be relied upon, as we're working from the bounding box's
# dimensions, not actual corners.
# Added FFX 3.3 alpha
def getNearestMapCorner(obj):
    objPos = Get_ObjectPos(obj)
    #datfiles.Mission_GetMapExtents() is more precise but doesn't work for the built-in maps,
    #which are zipped into missions.ff; in this case, use SkXMapInfo instead
    try:
        mapExtents = datfiles.Mission_GetMapExtents()
    except:
        mapExtents = ff.MapInfo['mapExtents']
    mapCorners = [(mapExtents[0], mapExtents[1], mapExtents[5]),
                  (mapExtents[0], mapExtents[4], mapExtents[5]),
                  (mapExtents[3], mapExtents[1], mapExtents[5]),
                  (mapExtents[3], mapExtents[4], mapExtents[5])]
    closest = 10000000
    closestCorner = (0,0,0)
    for c in mapCorners:
        dist = distanceSqPos2D(c, objPos)
        if dist < closest:
            closest = dist
            closestCorner = c
    return closestCorner

# Returns the edge/side position of the map nearest to the object, using getNearestMapCorner();
# cf. notes on this function.
# Added FFX 3.3 alpha
def getNearestMapSide(obj):
    objPos = Get_ObjectPos(obj)
    corner = getNearestMapCorner(obj)
    x = corner[0] - objPos[0]
    y = corner[1] - objPos[1]
    if x > y:
        return (objPos[0], corner[1], corner[2])
    #else:
    return (corner[0], objPos[1], corner[2])

(I suggest putting them between the distanceSqPos2D() function definition and the empty FFQ_LIGHTSOURCES = [] list.)


# New FFX 3.3: find all characters which are exiled and invisible;
# characters which are image displaced and invisble are not returned
# checkLiving: if at 1, k.o.'ed characters won't be returned
# lures: if set to 1, lures will be returned among the characters
# specialCharacters: if set to 0, tiggots, etc., won't be returned
def getAllDisabledCharacters(checkLiving = 1, lures = 0, specialCharacters = 1):
    chars = []
    for object in getAllCharacters(checkLiving, lures, specialCharacters):
        if ( Object_GetSecondaryStates(object) & SCSTATE_INVISIBLE ) and ( Object_GetPrimaryState(object) == PCSTATE_EXILE ):
            chars.append(object)
    return chars

(This one could go between getAllCharacters() and getAllVillains().)

EDIT: removed the FFX_DRIVER/VEHICLE_CUSTOM entries in ffx.py

lmalonsof


Mystik


BentonGrey

Ohh.....so freakin' awesome!  I really wish I had the time to put this through its paces.  I'll get to it as soon as I'm able though, and Batman will finally ride in style!

Mystik

okay  it works but only if I do the customization in the ffx file and not the ffxcustom2

Epimethee

Quote from: Thor Reborn on October 22, 2007, 01:20:10 PM
okay  it works but only if I do the customization in the ffx file and not the ffxcustom2
Oops. I forgot to remove the FFX_DRIVER_CUSTOM and FFX_VEHICLE_CUSTOM entries in ffx.py itself. Since they were read after the ffxcustom2.py file, they overwrote it.

The above ffx.py has been updated.

Mystik

just wanted to say thanx again

its so cool to see the batwing just fly in
or have some avengers load a quinjet

Epimethee

Thanks for the kind words; you just made my day. :)

BentonGrey

Okay, I finally have a chance to put this through its paces, but I have a couple questions.  First, do we still set up the vehicles as characters?  Second, how do we customize the driver and vehicles?  I see some things in Em's explanation there, but where do I put the multiple vehicle entries for someone like Batman?

Epimethee

Yes, you still need the vehicle attribute. Multiple vehicles for Bat would be:

FFX_DRIVER_CUSTOM=[
["default","cat jalopy","","","beside"],
["batman","batwing","batmobile","batwhatever","away"],
["types","UVehicle 1","UVehicle 2","UVehicle 3","AMethod"],
]


FFX_VEHICLE_CUSTOM=[
["default", "500"],
["batmobile", "500"],
["batwing", "300"],
["batwhatever", "2000"],
["types", "ACargo (lbs)"],
]

This would randomly spawn either the Batmobile, Batwing or Batwhatever. The vehicle would fly/dry to Batman from the side of the map when calls it. The numbers for cargo mean that there would be place in the B-Wing (...) for one big dude plus negligible additional mass (I'm talking to you, Ray Palmer!); two to three passengers could enter the Batmobile; a bunch could enter the "whatever" vehicle.

BentonGrey

Thanks Em!  That's a big help, I think I get it all now!