| 02-26-2009, 10:42 PM | #1 |
Hey there; I'm looking for feedback on my pet system, it's up and running but it seems a little lackluster to me. Any comments about the system, or what you'd like in a pet system would be great! <to clarify, this system summons a unit which is not controlable and it can be ordered around and will automatically follow you/attack units/be attackable.> Thanks; here's the code: JASS://CALLBACKS: //CALLBACK_STRUCT //Passes a struct of type Pet whenever a callback for this system is fired. //CALLBACK_RESPONDER //The unit targeted in the Pet aquires target callback. library PetLib uses Table, UnitIdLib globals private Pet array Pets private integer PetCount = 0 private timer PetTimer = CreateTimer() private constant real UPDATE_PERIOD = .5 private constant boolean USE_CUSTOM_FLAG_CALLS = true private constant integer MAX_PETS_PER_CASTER = 4 private constant real NEAR_RATIO = .25 //How close the pet should move based on //it's follow distance //Color to display for 100% hp. private constant integer HealthMaxR = 255 private constant integer HealthMaxG = 255 private constant integer HealthMaxB = 255 //Color to display for 0% hp. private constant integer HealthCriticalR = 255 private constant integer HealthCriticalG = 75 private constant integer HealthCriticalB = 75 //Flags for FLAG_AGRESSIVE constant integer MODE_UNKNOWN = 0 constant integer MODE_AGRESSIVE = 1 constant integer MODE_PASSIVE = 2 //Flags for FLAG_LINKED constant integer LINKED_UNKNOWN = 0 constant integer LINKED_CASTER = 1 //Flags for boolean flags; UNKNOWN != FALSE! constant integer FLAG_UNKNOWN = 0 constant integer FLAG_FALSE = 1 constant integer FLAG_TRUE = 2 //NOTE REGUARDING FOLLOWDIST: FLAG_UNKNOWN makes it have no followdist restrictions. //The established flag fields: constant integer FLAG_AGRESSIVE = 0x1000 //x constant integer FLAG_ONATTACK = 0x1001 //x constant integer FLAG_WHENATTACKED = 0x1002 //x constant integer FLAG_ONDEATH = 0x1003 //x constant integer FLAG_ONOWNERDEATH = 0x1004 //x constant integer FLAG_ONAQUIRE = 0x1005 //x constant integer FLAG_AQUIREDIST = 0x1006 //x constant integer FLAG_FOLLOWDIST = 0x1007 //x constant integer FLAG_SHARETARG = 0x1008 //x constant integer FLAG_NOTARGET = 0x1009 //x constant integer FLAG_SHOWDAMAGE = 0x1010 //x //The default settings of flags if you pass a null table for Flags constant integer AGRESSIVE_DEFAULT = MODE_PASSIVE constant integer ONATTACK_DEFAULT = FLAG_UNKNOWN constant integer WHENATTACKED_DEFAULT = FLAG_UNKNOWN constant integer ONDEATH_DEFAULT = FLAG_UNKNOWN constant integer ONOWNERDEATH_DEFAULT = FLAG_UNKNOWN constant integer ONAQUIRE_DEFAULT = FLAG_UNKNOWN constant integer AQUIREDIST_DEFAULT = 400 constant integer FOLLOWDIST_DEFAULT = 128 constant integer SHARETARG_DEFAULT = FLAG_FALSE constant integer NOTARGET_DEFAULT = FLAG_UNKNOWN constant integer SHOWDAMAGE_DEFAULT = FLAG_TRUE //Orders: constant integer PET_STOP = 851972 constant integer PET_MOVE = 851986 constant integer PET_ATTACK = 851983 constant integer PET_CAST = 1 private constant integer PercentRed = (HealthMaxR - HealthCriticalR) private constant integer PercentGreen = (HealthMaxG - HealthCriticalG) private constant integer PercentBlue = (HealthMaxB - HealthCriticalB) private unit array TargetArray private player ENUM_OWNING_PLAYER private group PET_TARGET_GROUP = CreateGroup() endglobals function interface PetFunction takes nothing returns nothing //EMPTY FUNCTIONS WHICH YOU NEED TO MAKE: //These are already integrated however they do nothing. //These are here because there is no standard and it's not really //related to this system directly. //Takes the pet's owner and returns the owner's target function GetUnitTarget takes unit owner returns unit return null endfunction private function AddPetToDamageStack takes Pet p returns nothing call RegisterUnitDamageEvent(p.pet, true, p.Flags[FLAG_ONATTACK]) endfunction private function AddPetToDamagedStack takes Pet p returns nothing call RegisterUnitDamageEvent(p.pet, false, p.Flags[FLAG_WHENATTACKED]) endfunction private struct Order integer order integer ov1 integer ov2 boolean immediate = false endstruct private function GetNextOrder takes Pet p returns Order return p.orders[p.norders-1] endfunction private function PopNextOrder takes Pet p returns Order local integer I = p.orders[p.norders-1] if(p.norders > 0)then set p.norders = p.norders - 1 endif return I endfunction private function OrdersPending takes Pet p returns boolean if(p.norders == 0)then return false endif return true endfunction //Yes it takes alot to pop an order! =] private function OrderStackAddInverse takes Pet p, Order o returns nothing local integer I = p.norders local integer temp //Add the element if there aren't any if(p.norders == 0)then set p.orders[0] = o set p.norders = p.norders + 1 return endif //Add the element and correctly handle it if there is 1 if(p.norders == 1)then set temp = p.orders[0] set p.orders[0] = o set p.orders[1] = temp set p.norders = p.norders + 1 return endif set p.orders[p.norders] = o //Put the last element at the start of the stack set temp = p.orders[0] set p.orders[0] = p.orders[p.norders] set p.orders[p.norders] = temp //Swap middle elements loop exitwhen(I == 1) set temp = p.orders[I-1] set p.orders[I-1] = p.orders[i] set p.orders[i] = temp set I = I - 1 endloop set p.norders = p.norders + 1 endfunction function PetDefaultFlags takes nothing returns Table local Table t = Table.create() set t[FLAG_AGRESSIVE] = AGRESSIVE_DEFAULT set t[FLAG_ONATTACK] = ONATTACK_DEFAULT set t[FLAG_WHENATTACKED]= WHENATTACKED_DEFAULT set t[FLAG_ONDEATH] = ONDEATH_DEFAULT set t[FLAG_ONOWNERDEATH]= ONOWNERDEATH_DEFAULT set t[FLAG_AQUIREDIST] = AQUIREDIST_DEFAULT set t[FLAG_FOLLOWDIST] = FOLLOWDIST_DEFAULT set t[FLAG_SHARETARG] = SHARETARG_DEFAULT set t[FLAG_NOTARGET] = NOTARGET_DEFAULT set t[FLAG_SHOWDAMAGE] = SHOWDAMAGE_DEFAULT return t endfunction private function OrderPet takes Pet p, Order o returns nothing local integer I = o.order local real q if(I == PET_STOP)then call IssueImmediateOrderById(p.pet, PET_STOP) elseif(I == PET_MOVE)then call IssuePointOrderById(p.pet, PET_MOVE, o.ov1, o.ov2) elseif(I == PET_ATTACK)then if(p.GetTarget() != null)then call IssueTargetOrderById(p.pet, PET_ATTACK, p.GetTarget()) endif elseif(I == PET_CAST)then if(p.GetTarget() != null)then call IssueTargetOrderById(p.pet, o.ov1, p.GetTarget()) endif endif endfunction //Logic note: //ox - (Cos(tt)*(mdist)*NEAR_RATIO) //Order unit to move to owner's location - circular ratio of mdist*NEAR_RATIO private function CheckPetBounds takes Pet p, real mdist returns nothing local real ox = GetUnitX(p.owner) local real oy = GetUnitY(p.owner) local real dx = ox-GetUnitX(p.pet) local real dy = oy-GetUnitY(p.pet) local real tt = Atan2(dy, dx) if(dx*dx+dy*dy > mdist*mdist)then call IssuePointOrderById(p.pet, PET_MOVE, ox-(Cos(tt)*mdist*NEAR_RATIO), oy-(Sin(tt)*mdist*NEAR_RATIO)) endif endfunction private function Enum_Enemies takes nothing returns boolean local unit u = GetFilterUnit() if(GetWidgetLife(u) > .405)then return IsPlayerEnemy(GetOwningPlayer(u), ENUM_OWNING_PLAYER) endif return false endfunction private function AquireTarget takes Pet p, real mdist, PetFunction callback returns unit local real px = GetUnitX(p.pet) local real py = GetUnitY(p.pet) set ENUM_OWNING_PLAYER = GetOwningPlayer(p.owner) call GroupClear(PET_TARGET_GROUP) call GroupEnumUnitsInRange(PET_TARGET_GROUP, px, py, mdist, Condition(function Enum_Enemies)) set CALLBACK_RESPONDER = FirstOfGroup(PET_TARGET_GROUP) set CALLBACK_STRUCT = p call callback.execute() return FirstOfGroup(PET_TARGET_GROUP) endfunction private function SetPetHealthiness takes Pet p returns nothing local real hpx = GetUnitState(p.pet, UNIT_STATE_LIFE)/GetUnitState(p.pet, UNIT_STATE_MAX_LIFE) call SetUnitVertexColor(p.pet, HealthCriticalR+R2I(hpx * PercentRed), HealthCriticalG+R2I(hpx * PercentGreen), HealthCriticalB+R2I(hpx * PercentBlue), 255) endfunction private function PetProcessing takes nothing returns nothing local Pet p local integer I = PetCount-1 local Order o local PetFunction pf loop set p = Pets[i] if(GetWidgetLife(p.pet) < .406)then // Pet dies set pf = p.Flags[FLAG_ONDEATH] set CALLBACK_STRUCT = p call pf.execute() call SetUnitVertexColor(p.pet, HealthMaxR, HealthMaxG, HealthMaxB, 255) set PetCount = PetCount - 1 set Pets[i] = Pets[PetCount] elseif(GetWidgetLife(p.owner) < .406)then // Owner dies set pf = p.Flags[FLAG_ONOWNERDEATH] set CALLBACK_STRUCT = p call pf.execute() else if(p.Flags[FLAG_SHOWDAMAGE] == FLAG_TRUE)then call SetPetHealthiness(p) endif if(p.Flags[FLAG_SHARETARG] == FLAG_TRUE)then call p.SetTarget(GetUnitTarget(p.owner)) else if(p.Flags[FLAG_AGRESSIVE] == MODE_AGRESSIVE)then if(p.GetTarget() == null)then call p.SetTarget(AquireTarget(p, p.Flags[FLAG_AQUIREDIST], p.Flags[FLAG_ONAQUIRE])) call IssueTargetOrderById(p.pet, PET_ATTACK, p.GetTarget()) elseif(GetWidgetLife(p.GetTarget()) < .406)then call p.SetTarget(AquireTarget(p, p.Flags[FLAG_AQUIREDIST], p.Flags[FLAG_ONAQUIRE])) call IssueTargetOrderById(p.pet, PET_ATTACK, p.GetTarget()) endif endif if(p.GetTarget() == null)then set CALLBACK_STRUCT = p set pf = p.Flags[FLAG_NOTARGET] call pf.execute() endif endif if(OrdersPending(p))then if(GetNextOrder(p).immediate == true)then set o = PopNextOrder(p) call OrderPet(p, o) elseif(GetUnitCurrentOrder(p.pet) == 0)then set o = PopNextOrder(p) call OrderPet(p, o) endif endif if(p.Flags[FLAG_FOLLOWDIST] != FLAG_UNKNOWN)then call CheckPetBounds(p, p.Flags[FLAG_FOLLOWDIST]) endif endif exitwhen(I == 0) set I = I - 1 endloop if(PetCount == 0)then call PauseTimer(PetTimer) endif endfunction struct Pet integer ID unit owner unit pet Table orders integer norders = 0 Table Flags static method create takes unit owner, integer pettype, real x, real y, Table flags returns Pet local Pet p = Pet.allocate() set p.owner = owner set p.pet = CreateUnit(GetOwningPlayer(owner), pettype, x, y, 90) call UnitAddAbility(p.pet, 'Aloc') call ShowUnit(p.pet, true) call UnitRemoveAbility(p.pet, 'Aloc') set p.ID = AssignUnitId(p.pet) if(flags == 0)then set flags = PetDefaultFlags() endif set p.Flags = flags if(flags[FLAG_ONATTACK] != FLAG_UNKNOWN)then call AddPetToDamageStack(p) endif if(flags[FLAG_WHENATTACKED] != FLAG_UNKNOWN)then call AddPetToDamagedStack(p) endif set Pets[PetCount] = p if(PetCount == 0)then call TimerStart(PetTimer, UPDATE_PERIOD, true, function PetProcessing) endif set PetCount = PetCount + 1 return p endmethod method UpdateFlags takes Table flags returns nothing if(flags[FLAG_ONATTACK] != FLAG_UNKNOWN)then call AddPetToDamageStack(this) endif if(flags[FLAG_WHENATTACKED] != FLAG_UNKNOWN)then call AddPetToDamagedStack(this) endif set .Flags = flags endmethod method UpdateFlag takes integer FlagNum, integer Val returns nothing set .Flags[FlagNum] = Val if(.Flags[FLAG_ONATTACK] != FLAG_UNKNOWN)then call AddPetToDamageStack(this) endif if(.Flags[FLAG_WHENATTACKED] != FLAG_UNKNOWN)then call AddPetToDamagedStack(this) endif endmethod method Command takes integer Command, integer Val1, integer Val2, boolean immediate returns nothing local Order o = Order.create() set o.order = Command set o.ov1 = Val1 set o.ov2 = Val2 set o.immediate = immediate call OrderStackAddInverse(this, o) endmethod method SetTarget takes unit u returns nothing set TargetArray[AssignUnitId(this.pet)] = u endmethod method GetTarget takes nothing returns unit return TargetArray[AssignUnitId(this.pet)] endmethod endstruct endlibrary |
| 02-27-2009, 01:03 PM | #2 |
It would be nice if you added a test map, I'm too lazy to implement this on my own and activate it but I want to test it and give some comments... |
| 02-27-2009, 08:00 PM | #3 |
| 02-28-2009, 05:32 PM | #4 |
Well, the pet attacks only the first target you acquire and doesn't stop until it dies. It also moves only when it's too far away from you. Personally, I'd like the pet to be a little "alive" like in the IndieSummon system by Vex - it should move periodically to a random location around its owner. Other features like defending its owner, attacking the unit you attacked (already in) or you casted a spell at would also be good. And, uh, you can't see the pet's hit points, nor its damage which is a bad side. |
