User:ArthurAutomaton/sandbox

From Valve Developer Community
< User:ArthurAutomaton
Revision as of 02:35, 30 August 2014 by ArthurAutomaton (talk | contribs) (note on how to change properties of a hero on their first spawn)
Jump to navigation Jump to search

Sandbox

Some notes to myself about modding DotA 2.

Creating a unit that's controllable by a specific player

This code creates a mud golem at (0, 0, 0) on the Radiant team and makes it controllable by player 0:

 local unit_team = DOTA_TEAM_GOODGUYS
 local unit_name = "npc_dota_neutral_mud_golem"
 local player = PlayerResource:GetPlayer(0)
 local point = Vector(0, 0, 0)
 
 local unit = CreateUnitByName(unit_name, point, true, player, player:GetAssignedHero(), unit_team)
 unit:SetControllableByPlayer(player:GetPlayerID(), true)


Relevant links:

Triggers and shared mutable state

Problem

You have two triggers, trigger_get and trigger_set. When trigger_set is triggered, you want to save some information in a certain global variable. When trigger_get is triggered, you should be able to access that information.

An approach that doesn't work

Warning.pngWarning:Here is a common approach that DOES NOT WORK

1. Create scripts/vscripts/trigger.lua with the following content:

 FOO = 0 -- we want to save information in this global variable
 
 function SetSharedVar ()
    FOO = 1
    print("SetSharedVar called; FOO = " .. FOO)
 end
 
 function GetSharedVar ()
    print("GetSharedVar called; FOO = " .. FOO)
 end

2. In Hammer, edit the properties of trigger_get as follows:

  • Set Entity Scripts = trigger.lua.
  • Add an output like this:
My Output Target Entity Target Input Parameter Delay Only Once
Io11.png OnStartTouch trigger_get CallScriptFunction GetSharedVar 0.00 No

3. In Hammer, edit the properties of trigger_set as follows:

  • Set Entity Scripts = trigger.lua.
  • Add an output like this:
My Output Target Entity Target Input Parameter Delay Only Once
Io11.png OnStartTouch trigger_set CallScriptFunction SetSharedVar 0.00 No

The expected outcome: When a unit steps on trigger_set and then afterwards steps on trigger_get, we expect to see the following output in the console:

SetSharedVar called; FOO = 1
GetSharedVar called; FOO = 1

What really happens: What we actually see in the console is this:

SetSharedVar called; FOO = 1
GetSharedVar called; FOO = 0

This shows that the assignment FOO = 1 made by SetSharedVar is NOT visible to trigger_get.

My guess about why this happens: When you "bind" some Lua code to an entity by setting its Entity Scripts property, that code is "private" to the entity: The entity initially has ITS OWN "blank" Lua environment (with only the library functions and the game API visible), which no other entity can access. When the map is loaded, the game engine populates this environment by running the Lua file specified by the Entity Scripts property. In our case, we set Entity Scripts = trigger.lua on both triggers, but they both get THEIR OWN COPIES of the global variables in trigger.lua. So the assignment FOO = 1 made by trigger_set is not visible to trigger_get since it just changes trigger_set's copy of FOO.

An approach that works

Follow steps 1-3 above but replace Step 2 with this:

2'. In Hammer, edit the properties of trigger_get as follows:

  • DO NOT SET THE Entity Scripts PROPERTY
  • Add an output like this (note that we now use trigger_set as the target):
My Output Target Entity Target Input Parameter Delay Only Once
Io11.png OnStartTouch trigger_set CallScriptFunction GetSharedVar 0.00 No

With this solution we get the expected output in the console. Why? If the guess above is correct, it's because we're now using trigger_set as the target, hence GetSharedVar now accesses trigger_set's copy of FOO.

Holdout stuff

  • For the bosses they use the vscripts key (in npc_units_custom.txt) to set the AI file that should be used.
    • In the AI files the global variable thisEntity (which is not initialized anywhere in the files) seems to refer to the unit itself.
    • Also it seems that if there's a function called Spawn in the file, then it will be called (with a certain table as argument) when the unit is spawned.
    • How does Ogre Magi get its AI file hooked up?

Passing an argument to a thinker

Problem: You want to start a thinker at a certain time during the game, and you want this thinker to have access to some information that's only available at this time.

Solution: Take advantage of the fact that functions in Lua can access variables of their enclosing functions:

 -- addon_game_mode.lua

 function Activate ()
    ListenToGameEvent("entity_killed", ExactDelayedRevenge, nil)
 end

 -- Three seconds after an entity has been killed its attacker will be killed
 function ExactDelayedRevenge (keys)
    local attacker = EntIndexToHScript(keys.entindex_attacker)
    GameRules:GetGameModeEntity():SetThink(function ()
          -- We can access attacker since it's a variable of our enclosing function
          if attacker and attacker:IsAlive() then
             attacker:ForceKill(false)
          end
    end, 3)
 end

Almost interactive development

Tip.pngTip:Typing "script_reload_code test" in the console will cause the Lua code in scripts/vscripts/test.lua to be executed.

Changing properties of a hero on their first spawn

Problem: You need to initialize a hero through Lua when it spawns for the first time (which is normally when it gets selected by a player through the hero selection screen).

Solution: Register a listener on the "npc_spawned" event. The listener should check whether the spawned NPC is a real hero, in which case it should initialize the NPC and mark it as initialized (so that it doesn't get re-initialized the next time it spawns):

 -- addon_game_mode.lua

 INIT_MARK = DoUniqueString("INIT") -- For marking a hero as initialized

 function Activate ()
    ListenToGameEvent("npc_spawned", OnNpcSpawned, nil)
 end

 function OnNpcSpawned (keys)
    local npc = EntIndexToHScript(keys.entindex)
    if npc and npc:IsRealHero() and not npc[INIT_MARK] then
       -- Initialize hero here, e.g.:
       npc:GetAbilityByIndex(0):SetLevel(3)
       npc[INIT_MARK] = true
    end
 end