| 04-21-2009, 05:29 PM | #1 |
I've just started working on some sort of coding environment which is supposed to make scripting custom AIs a bit easier. It currently looks like this and works fine so far: JASS:library AIEvents initializer Init requires HSAS function interface Acquire takes unit whichUnit, unit targetUnit returns nothing // covers Engaging and Target Changing function interface PointOrder takes unit whichUnit, real x, real y returns nothing // covers Disengaging function interface ImmediateOrder takes unit whichUnit returns nothing // covers Idling //! runtextmacro HSAS_Static("AI","32760","") struct AI unit baseUnit unit targetUnit integer status = 0 // 0 = idle, 1 = engaged, 2 = disengaging trigger array trig [3] Acquire engageActions Acquire changeTargetActions PointOrder disengageActions ImmediateOrder idleActions static method acquire takes nothing returns nothing local AI ai = GetAttachedStructAI(GetTriggeringTrigger()) set ai.targetUnit = GetEventTargetUnit() if ai.status == 0 then // Engaging set ai.status = 1 call ai.engageActions.evaluate(ai.baseUnit, ai.targetUnit) else // Changing Target call ai.changeTargetActions.evaluate(ai.baseUnit, ai.targetUnit) endif endmethod static method pointOrder takes nothing returns nothing local AI ai = GetAttachedStructAI(GetTriggeringTrigger()) if OrderId2String(GetIssuedOrderId()) == "move" then // Disengaging set ai.status = 2 call ai.disengageActions.evaluate(ai.baseUnit, GetOrderPointX(), GetOrderPointY()) endif endmethod static method immediateOrder takes nothing returns nothing local AI ai = GetAttachedStructAI(GetTriggeringTrigger()) if OrderId2String(GetIssuedOrderId()) == null then // Idling set ai.status = 0 call ai.idleActions.evaluate(ai.baseUnit) endif endmethod static method create takes unit whichUnit returns AI local AI ai = AI.allocate() // Unit setup set ai.baseUnit = whichUnit call AttachStructAI(whichUnit, ai) // Acquire set ai.trig[0] = CreateTrigger() call AttachStructAI(ai.trig[0], ai) call TriggerRegisterUnitEvent(ai.trig[0], whichUnit, EVENT_UNIT_ACQUIRED_TARGET) call TriggerAddAction(ai.trig[0], function AI.acquire) // Target Order set ai.trig[1] = CreateTrigger() call AttachStructAI(ai.trig[1], ai) call TriggerRegisterUnitEvent(ai.trig[1], whichUnit, EVENT_UNIT_ISSUED_POINT_ORDER) call TriggerAddAction(ai.trig[1], function AI.pointOrder) // Immediate Order set ai.trig[2] = CreateTrigger() call AttachStructAI(ai.trig[2], ai) call TriggerRegisterUnitEvent(ai.trig[2], whichUnit, EVENT_UNIT_ISSUED_ORDER) call TriggerAddAction(ai.trig[2], function AI.immediateOrder) return ai endmethod method onDestroy takes nothing returns nothing call DisableTrigger(.trig[0]) call DisableTrigger(.trig[1]) call DisableTrigger(.trig[2]) endmethod endstruct private function Init takes nothing returns nothing endfunction endlibrary What this does is basically assigning an AI struct to a unit and creating event triggers for certain events that might be useful for custom AIs. These events are accessable through interface functions. The code currently covers these four events: 1. A unit being engaged by another unit. Engaged and engaging unit will be passed through the interface. 2. A unit that already is engaged changing target. Engaged unit and new target will be passed. 3. A unit disengaging and retreating to its camp (= receiving order "move"). Disengaging unit and the camp's location will be passed. 4. A unit finishing disengaging and going into idle status again (= receiving order null). Only the idling unit will be passed. The struct also saves information like the unit's target and the unit's current state (i.e., idling, being engaged, retreating etc). Current problems are though that each AI struct will leak three (more to come) triggers that can't be destroyed. Using custom AIs for many units might cause serious lag or troubles with HSAS. The other problem is that I'm not really sure if it's useful at all. Of course there will be more events added to the struct that define a unit's behaviour (like maybe starting to cast a spell, the targeted unit trying to flee or something like that etc.) but many possibilities this script offers can actually easily be achieved by setting up the triggers yourself. Here is a sample script that demonstrates how to use the interface functions: JASS:scope Init initializer Init private function SampleIdle takes unit whichUnit returns nothing call BJDebugMsg(GetUnitName(whichUnit)+" idles again") endfunction private function SampleDisengage takes unit whichUnit, real x, real y returns nothing call BJDebugMsg(GetUnitName(whichUnit)+" disengages to "+R2S(x)+", "+R2S(y)) endfunction private function SampleChangeTarget takes unit whichUnit, unit targetUnit returns nothing call BJDebugMsg(GetUnitName(whichUnit)+" changed target to "+GetUnitName(targetUnit)) endfunction private function SampleEngage takes unit whichUnit, unit targetUnit returns nothing call BJDebugMsg(GetUnitName(whichUnit)+" engaged by "+GetUnitName(targetUnit)) endfunction private function Actions takes nothing returns nothing local AI ai = AI.create(gg_unit_nftr_0002) // setting up ai event action functions set ai.engageActions = Acquire.SampleEngage set ai.changeTargetActions = Acquire.SampleChangeTarget set ai.disengageActions = PointOrder.SampleDisengage set ai.idleActions = ImmediateOrder.SampleIdle call SetFloatGameState(GAME_STATE_TIME_OF_DAY, 6) endfunction private function Init takes nothing returns nothing set gg_trg_Init = CreateTrigger() call TriggerAddAction(gg_trg_Init, function Actions) endfunction endscope |
| 04-21-2009, 05:59 PM | #2 |
Regarding your trigger problem: As I imagine that all orders are given by the AI itself, it would probably be better to use function wrappers to detect for example orders. Besides if that isn't suitable, you could go for a single trigger approach and attach the structs to the units. |
| 04-21-2009, 06:53 PM | #3 | |
Looks neat. I was going to create something like this for small groups of units, and this is pretty cool actually. Quote:
Good thing that I recycle my units. I sometimes forget how easy it makes these things. ^_^ You could also add some wrapper function, so we can use some struct array and UnitUserData instead of HSAS. It would be nice, but not neccesacy, because its easy enough to change it now too. Anyways, I find this useful. |
| 04-21-2009, 08:50 PM | #4 |
Ok I'm using global triggers now and I wrapped them a bit too. Also I'm not passing anything through the events anymore as you can just use the usual event responses in the interfaced event functions (didn't know about that, sounds reasonable though). So that's basically just one function interface now. Added spell and order support, too: JASS:library AIEvents initializer Init requires HSAS function interface AIEvent takes nothing returns nothing //! runtextmacro HSAS_Static("AI","32760","") globals private trigger AcquireTrig = CreateTrigger() private group UnitsRegistered = CreateGroup() endglobals struct AI unit baseUnit = null unit targetUnit = null destructable targetDest = null item targetItem = null integer combatStatus = 0 // 0 = idle, 1 = engaged, 2 = disengaging, 3 = casting integer spellStatus = 0 // 0 = not casting, 1 = casting, 2 = channeling, 3 = effect AIEvent engageActions = 0 AIEvent changeTargetActions = 0 AIEvent disengageActions = 0 AIEvent idleActions = 0 AIEvent orderActions = 0 AIEvent spellCastActions = 0 AIEvent spellChannelActions = 0 AIEvent spellEffectActions = 0 AIEvent spellEndcastActions = 0 AIEvent spellFinishActions = 0 static method create takes unit whichUnit returns AI local AI ai = AI.allocate() set ai.baseUnit = whichUnit call AttachStructAI(whichUnit, ai) if not IsUnitInGroup(whichUnit, UnitsRegistered) then call TriggerRegisterUnitEvent(AcquireTrig, whichUnit, EVENT_UNIT_ACQUIRED_TARGET) call GroupAddUnit(UnitsRegistered, whichUnit) endif return ai endmethod method onDestroy takes nothing returns nothing set .baseUnit = null endmethod endstruct private function UnitHasAI takes nothing returns boolean local AI ai = GetAttachedStructAI(GetTriggerUnit()) return ai.baseUnit == GetTriggerUnit() endfunction private function Acquire takes nothing returns nothing local AI ai = GetAttachedStructAI(GetTriggerUnit()) set ai.targetUnit = GetEventTargetUnit() if ai.combatStatus == 0 then // Engaging set ai.combatStatus = 1 call ai.engageActions.evaluate() else // Changing Target call ai.changeTargetActions.evaluate() endif endfunction private function Order takes nothing returns nothing local AI ai = GetAttachedStructAI(GetTriggerUnit()) set ai.targetUnit = GetOrderTargetUnit() set ai.targetDest = GetOrderTargetDestructable() set ai.targetItem = GetOrderTargetItem() if OrderId2String(GetIssuedOrderId()) == "move" then // Disengaging set ai.combatStatus = 2 call ai.disengageActions.evaluate() elseif OrderId2String(GetIssuedOrderId()) == null then // Idling set ai.combatStatus = 0 call ai.idleActions.evaluate() endif call ai.orderActions.evaluate() endfunction //! textmacro Spell takes MOMENT, STATUS private function Spell$MOMENT$ takes nothing returns nothing local AI ai = GetAttachedStructAI(GetTriggerUnit()) set ai.targetUnit = GetSpellTargetUnit() set ai.targetDest = GetSpellTargetDestructable() set ai.targetItem = GetSpellTargetItem() set ai.spellStatus = $STATUS$ call ai.spell$MOMENT$Actions.evaluate() endfunction //! endtextmacro //! runtextmacro Spell("Cast", "1") //! runtextmacro Spell("Channel", "2") //! runtextmacro Spell("Effect", "3") //! runtextmacro Spell("Endcast", "0") //! runtextmacro Spell("Finish", "0") private function Init takes nothing returns nothing local trigger trig local boolexpr hasai = Condition(function UnitHasAI) // Acquire call TriggerAddAction(AcquireTrig, function Acquire) call TriggerAddCondition(AcquireTrig, hasai) // Order set trig = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_ISSUED_ORDER) call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER) call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER) call TriggerAddAction(trig, function Order) call TriggerAddCondition(trig, hasai) // Spell //! textmacro SpellInit takes MOMENT, MOMENT_CAPS set trig = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_$MOMENT_CAPS$) call TriggerAddAction(trig, function Spell$MOMENT$) call TriggerAddCondition(trig, hasai) //! endtextmacro //! runtextmacro SpellInit("Cast", "CAST") //! runtextmacro SpellInit("Channel", "CHANNEL") //! runtextmacro SpellInit("Effect", "EFFECT") //! runtextmacro SpellInit("Endcast", "ENDCAST") //! runtextmacro SpellInit("Finish", "FINISH") set trig = null endfunction endlibrary |
