PDA

View Full Version : [Guide] Making multiple creep spawning and pathing



tojooko
06-07-2011, 08:55 PM
Introduction

So, I decided to write it inspired by i927 with his question:


Question #1
How do I make a creepspawner so it will spawn a single creep every X seconds (e.g. 5) which creeps will walk in a definned node path and then disappearing when reach a spot or position or pathnode?


I wanted to make code, which doesn't requre milion of entities like: point1a, point1b,... Well, I think I managed to do that. So, let's check it.

Idea description

First of all we have 2 gadgets: LaneStart and LanePoint. LaneStart spawns creeps and send them to first Point. LanePoint hasn't much code, it only redirects creep to move along (or kills if it's last one in chain).

We see that our assumption is not so complicated. Our next problem is how to make from group of gadgets a path which creeps will follow. So that's where the most interesting code is hidden, and where I found two diffrent ways to solve this problem. I'll describe here first invented way, second will be in described Possible Tweaks topic.

So, in this way I use State_CreateLane entity. It's applied first time by LaneStart on first LanePoint (let's call it locally A). It searchs for the closest free LanePoint (B) to the first one (A) and set's B as A's proxy_entity value. Also it marks B as Used so it can be no longer found by State_CreateLane. It's necessary, becouse State_CreateLane is recursive function (http://en.wikipedia.org/wiki/Recursion), and it's last step is to apply new State_CreateLane to B point. Now it's going simmilar, but this time we find new LanePoint (C) and set it as B's proxy entity. And so on... What is more when we find last LanePoint it's marked as Deadly and it kills creeps insted of sending them farther.

I think it will be clearer when we'll see it in code. So there is it:

Code

LaneStart.entity:


<gadget
name="LaneStart"
model=""
canattack="false"
canrotate="false"
drawonmap="false"
hoveronmap="false"
icon=""
immunity="AllImmunity"
stealthtype="Unrevealable"
invulnerable="true"
isselectable="false"
>
<!-- Trick used to detect's when game starts (<onspawn> is too early, even before game lobby) -->
<aura state="State_OngameStart" targetscheme="self" ignoreinvulnerable="true" />

</gadget>
state_ongamespawn.entity:


<state
name="State_OngameStart"
ishidden="true"
>
<oninflict> <!-- search for closest LanePoint (note ally_free_points)-->
<areaofeffect
global="true"
targetselection="closest"
targetscheme="ally_free_points"
effecttype="SuperiorMagic"
ignoreinvulnerable="true"
maxtotalimpacts="1"
center="source_position"
>
<applystate target="source_entity" name="State_Spawning" continuous="true" proxy="target_entity" statelevel="source_level"/> <!-- turn on spawning -->
<setactivemodifierkey entity="target_entity" name="Used"/> <!-- mark found Point as used -->
<applystate name="State_CreateLane" duration="10" target="target_entity"/> <!-- start making a lane -->
</areaofeffect>
</oninflict>
</state>

LanePoint.entity


<gadget
name="LanePoint"
model=""
canattack="false"
canrotate="false"
drawonmap="false"
hoveronmap="false"
icon=""
immunity="AllImmunity"
stealthtype="Unrevealable"
invulnerable="true"
isselectable="false"
unittype="LanePointType"
>
<!-- notice unittype="LanePointType" -->
<modifier key="Used" modpriority="1" unittype="UsedPoint" >
<aura state="State_MoveAura" radius="20" targetscheme="laneunits" />
</modifier>
<modifier key="Deadly" modpriority="1" unittype="UsedPoint">
<aura state="State_DeadlyAura" radius="20" targetscheme="laneunits" />
</modifier>
</gadget>
state_createlane.entity


<state
name="State_CreateLane"
>
<oninflict> <!-- search for other LanePoint-->
<areaofeffect
targetselection="closest"
targetscheme="ally_free_points"
effecttype="SuperiorMagic"
ignoreinvulnerable="true"
maxtotalimpacts="1"
center="this_owner_position"
global="true"
>
<setproxy entity="this_owner_entity" target="target_entity"/> <!-- Store next point in proxy -->
<setactivemodifierkey entity="target_entity" name="Used"/> <!-- Mark as Used -->
<applystate name="State_CreateLane" target="target_entity" duration="10"/> <!-- Go for next Point (recursion) -->
</areaofeffect>

<pushentityproxy entity="this_owner_entity"/> <!-- Check does this point has defined stack_entity -->
<compare a="stack_entity" b="0" op="eq">
<setactivemodifierkey entity="this_owner_entity" name="Deadly"/> <!-- False means it's last point - make it deadly -->
</compare>
<expire/> <!-- expire this state, it have done its job -->
</oninflict>

</state>

LanePoint's auras:

state_moveaura.entity:


<state
name="State_MoveAura"
ishidden="true"
>
<oninflict>
<cancel entity="this_owner_entity"/>
<pushentityproxy entity="source_entity"/> <!-- get proxy from LanePoint entity and push it on stack-->
<order command="move" force="true" source="this_owner_entity" target="stack_position"/>
</oninflict>
</state>

state_deadlyaura.entity:


<state
name="State_DeadlyAura"
ishidden="true"
>
<oninflict>
<delete source="source_entity" target="this_owner_entity"/>
</oninflict>
</state>

state_spawning.etity:


<state
name="State_Spawning"
impactinterval="5000"
ishidden="true"
>
<oninflict>
<spawnunit name="Neutral_snottling,Neutral_Earthoc" target="this_owner_entity" pushentity="true" team="0"/>
<order command="move" force="true" source="stack_entity" target="proxy_position" />
<applystate target="stack_entity" name="State_LaneUnit" continuous="true" /> <!-- Mark as LaneUnit - won't follow path without it -->
</oninflict>
<onimpact>
<spawnunit name="Neutral_snottling,Neutral_Earthoc" target="this_owner_entity" pushentity="true" team="0"/>
<order command="move" force="true" source="stack_entity" target="proxy_position" />
<applystate target="stack_entity" name="State_LaneUnit" continuous="true" />
</onimpact>
</state>
state_laneunit.entity:


<state
name="State_LaneUnit"
ishidden="true"
unittype="LaneUnit"
>
</state>
base.gamemechanics additions in <!-- Target Schemes -->:


<targetscheme name="ally_free_points" allow="LanePointType" restrict="enemy,UsedPoint"/>
<targetscheme name="laneunits" allow="LaneUnit" restrict=""/>

Comments
Well, in this code are some additional functionalities. First of them: ally_free_points targetscheme contains "ally" word. That means Lanes don't have infuence on each other as long as they are in other teams. Becouse team value can be defined in 0-255 range (0,1,2,255 are used by default) we can make over 200 lanes ( can't even imagine it). Well, in this code creeps are "capturable" by other lane (creep doesn't recognise his lane's Points and interact with all of them). It can be fixed, bt more about it in "Possible Tweaks" topic

Second functionality is hidden in State_OngameStart and State_Spawning. We can see in State_OngameStart:
<applystate target="source_entity" name="State_Spawning" continuous="true" proxy="target_entity" statelevel="source_level"/> and in State_Spawning
<spawnunit name="Neutral_snottling,Neutral_Earthoc" ...What does it mean? It take's LaneStart's level and based on that spawns defined creep. So for level equal 1 it spawns Neutral_snottling and for 2 Neutral_Earthoc. And LaneStart's level can be easly defined in Map Editor:smile: (how-to below)

Oloko's comment about maximum level:


I'm not too sure about the state level set to spawn different waves. I guess it works fine if you have the right level limit set in your config file. The annoying thing is that your max level set for your hero may be too low for the number of different creeps you want. Otherwise I think it's a good way to do it.

Well that means whan you want to spawn over 25 creeps types (and you didn't changed maximul level value), you'll have to make new LaneStart, new State_OngameStart and new State_Spawning entities

Possible tweaks
Sometimes you will need quite diffrent functionality. There you can find already invented tricks:

1. First will be promised by me binding creep to one lane. To do it we must edit State_LaneUnit and auras. We are going to store StartPoint's level in creep's counter value and later compare does it is equalto LanePoint's level (so we just check does LanePoint is from proper lane)
state_laneunit.entity:


<state
name="State_LaneUnit"
ishidden="true"
unittype="LaneUnit"
maxcharges="255"
counterpercharge="1"
>
<oninflict>
<addcharges count="source_team"/> <!-- source = LaneStart -->
</oninflict>
</state>

state_moveaura.entity


<state
name="State_MoveAura"
ishidden="true"
>
<oninflict>
<compare a="owner_counter" b="source_team" op="eq">
<cancel entity="this_owner_entity"/>
<pushentityproxy entity="source_entity"/> <!-- get proxy from LanePoint entity and push it on stack-->
<order command="move" force="true" source="this_owner_entity" target="stack_position"/>
</compare>
</oninflict>
</state>
state_deadlyaura.entity:


<state
name="State_DeadlyAura"
ishidden="true"
>
<oninflict>
<compare a="owner_counter" b="source_team" op="eq">
<delete source="source_entity" target="this_owner_entity"/>
</compare>
</oninflict>
</state>


2. This tweak will solve making "slalom path" Way presented above was searching for closest LanePoint. We can force them defined order by giving every LanePoint a level value. Obviusly we have to give them successive natural numbers. Unfotunately we can have only 25 LanePoints by default (Oloko's comment above).
It seems HoN engine doesn't like recursive functions so we are going to do it in other way. Every LanePoint will check all other LanePoints and save the one, which is next to it. Targetscheme in first areaofeffect must be changed to ally_points becouse we must give all LanePoints a proxy. We'll edit State_OngameStart and remove State_CreateLane.


state_ongamestart.entity:


<state
name="State_OngameStart"
ishidden="true"
>
<oninflict>
<areaofeffect
global="true"
targetselection="all"
targetscheme="ally_points"
effecttype="SuperiorMagic"
ignoreinvulnerable="true"
maxtotalimpacts="999"
maximpactspertarget="1"
>
<compare a="target_level" b="1" op="eq"> <!-- search for 1st point -->
<applystate target="source_entity" name="State_Spawning" continuous="true" proxy="target_entity" statelevel="source_level"/> <!-- turn on spawning -->
<setactivemodifierkey entity="target_entity" name="Used"/> <!-- Mark as Used -->
</compare>

<setvar0 a="target_level" b="1" op="add"/>
<setent0 entity="target_entity"/>

<areaofeffect
global="true"
targetselection="all"
targetscheme="ally_free_points"
effecttype="SuperiorMagic"
ignoreinvulnerable="true"
maxtotalimpacts="999"
maximpactspertarget="1"
ignore="ent0"
>
<compare a="target_level" b="var0" op="eq"> <!-- search for next point -->
<setproxy entity="ent0" target="target_entity"/> <!-- Store next point in proxy -->
<setactivemodifierkey entity="target_entity" name="Used"/> <!-- Mark as Used -->
</compare>
</areaofeffect>
</areaofeffect>

</oninflict>
</state>

base.gamemechanics additions in <!-- Target Schemes -->:


<targetscheme name="ally_free_points" allow="LanePointType" restrict="enemy,UsedPoint"/>
<targetscheme name="ally_points" allow="LanePointType" restrict="enemy"/>
<targetscheme name="laneunits" allow="LaneUnit" restrict=""/>

Example (http://www.mediafire.com/?04pn6943ufhhwgh)

If you made any usefull tweak and want to share it please post it in this thread:smile:

How-to in Map Editor
Well, spawning random entities and later editing it in entitylist file can be annoying. Other way to spawn new entities in map editor is by using console (CTRL + F8 as usual). There are group of variables starting with le_entity (type it and press TAB to show all of them)We are going to use le_entityType. For example to force Create tool to spawn LanePoint just type "le_entityType LanePoint" (without ""). Unfortunately we can't do same with le_entityTeam (doesn't work at least for me) so we still have to edit entitylist file.
Using TransformXY tool with SHIFT key is very usefull too.

Editing level is very simple, just change it in dedicated window.

Example Download (http://www.mediafire.com/?rmoh5cj9ty5lejc)

Hall of fame
I want to say thanks to i927 for inspiration and Schm0ftie for making great void gadget template.
Big thanks to Oloko for suggestions, comment and fixes

I hope you enjoyed reading my guide and it will be usefull. Waiting for some feedback with tons of bugs :D

Schm0ftie
06-08-2011, 02:05 AM
Very nice guide, like the recursive way =) I got two things:

1. Could you remove the <xml.....> tags in each [php] tag
so formating will be applied?

2. You use a Gadget Imunity Type = DefuseKitImunity which
was created by me for my map. It was to prevent gadgets
being killed by a item heroes can use to kill different gadgets.
I don't know if the name at least realy fit here =)

Ah btw. wohoooo Hall of Fame x)

tojooko
06-08-2011, 05:01 AM
Fixed <xml> tags, thx for it.
I'll try with AllImmunity but just later, all in all it works even with your:P (I suppose AoE spells gonna destroy it)
Thx for feedback

Oloko
06-08-2011, 07:23 AM
Really good guide.

One thing I would change is the way you detect the last point. Rather than doing a <compare /> on each "call", you might want to do it like this:


<areaofeffect
targetselection="closest"
targetscheme="ally_free_points"
effecttype="SuperiorMagic"
ignoreinvulnerable="true"
maxtotalimpacts="1"
center="this_owner_position"
global="true"
>
<setproxy entity="this_owner_entity" target="target_entity"/> <!-- Store next point in proxy -->
<setactivemodifierkey entity="target_entity" name="Used"/> <!-- Mark as Used -->
<applystate name="State_CreateLane" target="target_entity" duration="10"/> <!-- Go for next Point (recursion) -->
</areaofeffect>
<else>
<setactivemodifierkey entity="this_owner_entity" name="Deadly"/>
</else>So the script will only trigger if it can't find another node.

I'm not too sure about the state level set to spawn different waves. I guess it works fine if you have the right level limit set in your config file. The annoying thing is that your max level set for your hero may be too low for the number of different creeps you want. Otherwise I think it's a good way to do it.

There is also a lot of map that can have a issue with path blocking. When a path gets blocked, units will simply stop moving because they can't find a path anymore. Even after the path gets cleared, they will simply sit there doing nothing. So in maps with corridors or with lots of units, this will often happen. That's why you might want to have a way to resend an order to units from time to time.
The easiest way is to put a state on the unit that will order it to move to a position every X second. This of course will use more resources since each creep will have that state.
There is also the way of doing a massive AOE every X second to order each unit to move again. I don't know if its better than the state way of doing it.
There are probably other ways of doing it, but those are the one that comes to mind first.

Anyway, great guide!

tojooko
06-08-2011, 07:59 AM
One thing I would change is the way you detect the last point. Rather than doing a <compare /> on each "call", you might want to do it like this:


<areaofeffect
targetselection="closest"
targetscheme="ally_free_points"
effecttype="SuperiorMagic"
ignoreinvulnerable="true"
maxtotalimpacts="1"
center="this_owner_position"
global="true"
>
<setproxy entity="this_owner_entity" target="target_entity"/> <!-- Store next point in proxy -->
<setactivemodifierkey entity="target_entity" name="Used"/> <!-- Mark as Used -->
<applystate name="State_CreateLane" target="target_entity" duration="10"/> <!-- Go for next Point (recursion) -->
</areaofeffect>
<else>
<setactivemodifierkey entity="this_owner_entity" name="Deadly"/>
</else>So the script will only trigger if it can't find another node.

Realy thx, didn't know about it, fixed
Well, it don't want to work. Found example of such code in State_CorruptedDisciple_Ability4_Self (it used <compare to check result of <areaofeffect) and it should work, but I suppose recursion kills result of <areaofeffect (result is proper in State_OngameStart but it's not in State_CreateLane). Seems I have to stick to orginal code until we invent something new :(



I'm not too sure about the state level set to spawn different waves. I guess it works fine if you have the right level limit set in your config file. The annoying thing is that your max level set for your hero may be too low for the number of different creeps you want. Otherwise I think it's a good way to do it.

Going to add it to "Comments"



There is also a lot of map that can have a issue with path blocking. When a path gets blocked, units will simply stop moving because they can't find a path anymore. Even after the path gets cleared, they will simply sit there doing nothing. So in maps with corridors or with lots of units, this will often happen. That's why you might want to have a way to resend an order to units from time to time.
The easiest way is to put a state on the unit that will order it to move to a position every X second. This of course will use more resources since each creep will have that state.
There is also the way of doing a massive AOE every X second to order each unit to move again. I don't know if its better than the state way of doing it.
There are probably other ways of doing it, but those are the one that comes to mind first.

Will add it to "Possible Tweaks" soon™.

I knew Hall of Fame won't wait long for you Oloko ;)

an7hraxjax
06-08-2011, 05:24 PM
Oh really nice guide !
I barely have time to test it now, but I'll test it some day..
I did quite some research as well on ways to implement algorithms in HoN :)
It can be hard, but satisfying. (that's what she said)