[Solved] Trouble with heroes in a user script

In this forum you will find and post information regarding the modding of Star Wars Battlefront 2. DO NOT POST MOD IDEAS/REQUESTS.

Moderator: Moderators

Post Reply
Sporadia
Corporal
Corporal
Posts: 151
Joined: Thu Jan 24, 2019 11:02 pm
Projects :: No Mod project currently
Games I'm Playing :: None
xbox live or psn: No gamertag set

[Solved] Trouble with heroes in a user script

Post by Sporadia »

I made an AI hero user script. It has a few different modes like normal AI heroes or BF1 style heroes etc. Every mode works on it's own. Now I'm trying to implement a dice roll system which randomly selects a different mode at the start of each mission. The random number generation and switching modes also isn't a problem. Some modes let the player unlock heroes. Other modes need player heroes turned off eg BF1.

At the moment, when I play an instant action playlist, I go through a few missions where the player should be allowed to unlock the hero and they work fine. Then I'll roll BF1 and it will turn off the player's ability to unlock heroes so it's AI only for that mission (also what it should be doing). Then, the next mission in the playlist where the player should be able to unlock heroes, it doesn't work. The heroes stay locked, like it somehow remembers that I'd turned them off in a previous mission. I have been using EnableSPScriptedHeroes() to turn off the player's ability to unlock heroes. I can go through several missions in a row without hitting EnableSPScriptedHeroes(), then one mission will use it and suddenly it's permanently on. I can't tell what's happening.

So now I've made a test user script where I've taken out all the timers and pretty much everything I don't need in order to test this. It has 2 modes: in mode 1, I want AI heroes to be on and player heroes to be off; in mode 2, I want AI heroes to be off and player heroes to be on. The reason I'm alternating the AI heroes is so I can tell what mode the mission is supposed to be in when I'm playing it. This is the test user script (cut into 4 parts):

Section 1) This is the user script header. It's not a section of interest but I'm providing it for context.
Hidden/Spoiler:
[code]
-- SPD Hero User Script: Test Version For Turning Scripted Heroes On And Off

-- =====================================================================
-- ==================== Standard user script header ====================
-- =====================================================================

local SPD_scriptnum = 12 -- change this value if you change the file from user_script_12 to another number
-- it will update the number shown in the debug log

print("SPD (user script "..SPD_scriptnum.."): SPD user script entered")

if SPD_Test then
print("SPD (user script "..SPD_scriptnum.."): Warning: Function SPD_Test defined by another script")
else
function SPD_Test()
print("SPD (user script "..SPD_scriptnum.."): SPD test function called")
end
end

-- =====================================================================
-- =====================================================================
[/code]
Section 2) This is the random number generation which selects the mode. I'm confident it works.
Hidden/Spoiler:
[code]
-- ==========================================================
-- ==================== Global Variables ====================
-- ==========================================================

-- standard warning messages
if SPD_randomNum then
print("SPD (user script "..SPD_scriptnum.."): Warning: Variable SPD_randomNum already exists")
end
if SPD_modeSelect then
print("SPD (user script "..SPD_scriptnum.."): Warning: Variable SPD_modeSelect already exists")
end

-- globals
SPD_randomNum = math.random(1, 2) -- a random number between 1 and 2
print("SPD (user script "..SPD_scriptnum.."): Random Number: "..SPD_randomNum)
SPD_modeSelect = SPD_randomNum

-- ==========================================================
-- ==========================================================
[/code]
Section 3) This is a whole bunch of functions which spawn AI heroes. I'm confident this works too. Also the first of these functions is referencing globals from another user script (SPB). SPB's user script has some code injected into SetHeroClass which copies the name of the hero class and saves it (in SPB_Hero1Store or SPB_Hero2Store, depending on the team). Everything else in SPB is unrelated to heroes; it's just replacing super battle droids and magnagaurds etc with patched versions. It does this for Han Solo which is why it hacks SetHeroClass.
Hidden/Spoiler:
[code]
-- ========================================================
-- ==================== SPD Functions =====================
-- ========================================================


-- ========== SPD_GetHero(team) ==========
-- function to get the hero names from the SPB user script
-- used for spawning AI heroes
-- returns the name of a given team's hero class as a string

-- standard warning message
if SPD_GetHero then
print("SPD (user script "..SPD_scriptnum.."): Warning: Function SPD_GetHero already exists")
end

function SPD_GetHero(team)
if team == 1 then
return SPB_Hero1Store -- these are handled by another user script (SPB)
elseif team == 2 then
return SPB_Hero2Store -- these are handled by another user script (SPB)
else
return nil
end
end

-- =======================================


-- ========== SPD_SpawnAIHero(team, skipFlag, skipNum) ==========
-- function to spawn AI heroes for a given team
-- chooses an AI team member and makes them a hero
-- returns the class number of the hero they've been set to
-- for general use, treat this function like it's SPD_SpawnAIHero(team)
-- for respawns, use SPD_RespawnAIHero(team, heroClassNum) instead
-- SPD_RespawnAIHero makes use of the other arguments

-- standard warning message
if SPD_SpawnAIHero then
print("SPD (user script "..SPD_scriptnum.."): Warning: Function SPD_SpawnAIHero already exists")
end

function SPD_SpawnAIHero(team, skipFlag, skipNum)
local hero = SPD_GetHero(team)
local teamSize = GetTeamSize(team)

-- check for errors
if not (teamSize and hero) then
print("SPD (user script "..SPD_scriptnum.."): Error: Failure of SPD_GetHero or GetTeamSize in SPD_SpawnAIHero")
return nil
elseif not ((team == 1) or (team == 2)) then
print("SPD (user script "..SPD_scriptnum.."): Warning: SPD_SpawnAIHero function called for a team other than 1 or 2")
return nil

else
local previousClassNum = nil
local heroClassNum = nil
local success = nil

-- go through every member of the team looking for AI
for i = 0, teamSize - 1 do
local charIndex = GetTeamMember(team, i) -- index is individual player/AI data
local charUnit = GetCharacterUnit(charIndex) -- unit is the spawned player number

-- filters
if not success then -- only set one AI team member as a hero
if not charUnit then -- skip units that have already spawned
if not IsCharacterHuman(charIndex) then -- don't apply to humans

-- save the unit's previous class number (GetCharacterClass returns a number not a string)
previousClassNum = GetCharacterClass(charIndex)

-- filter for respawn arguments if provided
if not (skipFlag and (previousClassNum == skipNum)) then

-- change the unit's class to this team's hero
SelectCharacterClass(charIndex, hero)
-- save the new class number
heroClassNum = GetCharacterClass(charIndex)

-- flag that an AI member has been found and set as a hero
success = true
end
end
end
end
end -- for loop end

-- check for errors
if not (success and heroClassNum) then
print("SPD (user script "..SPD_scriptnum.."): Failed to spawn AI hero for team "..team..". Could not find a suitable team member")
return nil
else
return heroClassNum
end
end
end

-- ==============================================================


-- ========== SPD_RespawnAIHero(team, heroClassNum) ==========
-- a more reliable function to spawn AI heroes
-- for the first spawn use SPD_SpawnAIHero(team) instead
-- exists because calling SPD_SpawnAIHero immediately after a hero dies can fail
-- requires the number of the hero class (returned from SPD_SpawnAIHero) in order to function

-- standard warning message
if SPD_RespawnAIHero then
print("SPD (user script "..SPD_scriptnum.."): Warning: Function SPD_RespawnAIHero already exists")
end

function SPD_RespawnAIHero(team, heroClassNum)
-- check for errors
if not (((team == 1) or (team == 2)) and heroClassNum) then
print("SPD (user script "..SPD_scriptnum.."): Warning: SPD_RespawnAIHero function has incorrect arguments")
return nil

else
local newHeroClassNum = nil
-- spawn the AI hero but skip any AI which is already set to the hero class
newHeroClassNum = SPD_SpawnAIHero(team, true, heroClassNum)

-- check that the hero class numbers match
if not (newHeroClassNum == heroClassNum) then
print("SPD (user script "..SPD_scriptnum.."): Error: a mistake has been made with SPD_RespawnAIHero function")
return nil
end

return true
end
end

-- ===========================================================


-- ========================================================
-- ========================================================
[/code]
Section 4) This is what I want everybody to look at. ScriptPostLoad() is not spawning AI heroes every mission so that much is working. It only does it when SPD_modeSelect == 1. But something about this user script is making it so that if EnableSPScriptedHeroes() get's used one mission, then every mission after that will remember it's been used and the AI heroes will stay locked. Even though EnableSPScriptedHeroes() is inside 'if SPD_modeSelect == 1 then' with the code that spawns AI heroes. I don't know what's wrong with it.
Hidden/Spoiler:
[code]
-- ========================================================
-- ==================== Function Hacks ====================
-- ========================================================


-- ========== ScriptPostLoad() ==========

if ScriptPostLoad then
-- standard warning message
if SPD_ScriptPostLoad then
print("SPD (user script "..SPD_scriptnum.."): Warning: Function SPD_ScriptPostLoad already exists")
end
-- backup the normal ScriptPostLoad function
SPD_ScriptPostLoad = ScriptPostLoad

-- redefine ScriptPostLoad to something new
ScriptPostLoad = function()
-- run the mission lua ScriptPostLoad
SPD_ScriptPostLoad()

-- only do these changes when in SP
if not ScriptCB_InMultiplayer() then

-- ==== Code Injected At End ====

-- maybe not a perfect 1 in 2 chance but close enough
if SPD_modeSelect == 1 then

-- keep track of the hero class numbers
local heroClassNum1 = nil
local heroClassNum2 = nil

-- spawn AI heroes for the first time
heroClassNum1 = SPD_SpawnAIHero(1)
heroClassNum2 = SPD_SpawnAIHero(2)

-- respawn AI heroes when they die
OnCharacterDeath(
function(player, killer)
local team = GetCharacterTeam(player)

-- filters
if team == 1 then -- team 1
if GetCharacterClass(player) == heroClassNum1 then -- team 1 hero

-- respawn the AI hero for team 1
SPD_RespawnAIHero(1, heroClassNum1)
end

-- filters
elseif team == 2 then -- team 2
if GetCharacterClass(player) == heroClassNum2 then -- team 2 hero

-- respawn the AI hero for team 2
SPD_RespawnAIHero(2, heroClassNum2)
end
end
end
)

EnableSPScriptedHeroes()
print("SPD (user script "..SPD_scriptnum.."): EnableSPScriptedHeroes() used")

end

-- ==============================

end
end

else
print("SPD (user script "..SPD_scriptnum.."): Error: Unable to find ScriptPostLoad()")
end

-- ======================================


-- ========================================================
-- ========================================================
[/code]
The user script is called SPD if I haven't made that obvious.

I'm trying to come up with alternative ways to lock heroes in a mission than using EnableSPScriptedHeroes(). I've tried hacking and preventing EnableSPHeroRules() like so:
Hidden/Spoiler:
[code]
-- ========== EnableSPHeroRules() ==========

if EnableSPHeroRules then
-- standard warning message
if SPD_EnableSPHeroRules then
print("SPD (user script "..SPD_scriptnum.."): Warning: SPD_EnableSPHeroRules already exists")
end

-- backup EnableSPHeroRules
SPD_EnableSPHeroRules = EnableSPHeroRules

-- hack code into the beginning of EnableSPHeroRules
EnableSPHeroRules = function (...)
if not ScriptCB_InMultiplayer() then
print("SPD (user script "..SPD_scriptnum.."): SPD_EnableSPHeroRules")

-- don't allow EnableSPHeroRules() if the mode is 1
if not (SPD_modeSelect == 1) then
-- run EnableSPHeroRules
SPD_EnableSPHeroRules(unpack(arg))
print("SPD (user script "..SPD_scriptnum.."): SPD_EnableSPHeroRules ran")
else
print("SPD (user script "..SPD_scriptnum.."): SPD_EnableSPHeroRules prevented")
return
end
end
end
else
print("SPD (user script "..SPD_scriptnum.."): Error: Unable to find EnableSPHeroRules()")
end

-- =========================================


-- ========== ScriptPostLoad() ==========

if ScriptPostLoad then
-- standard warning message
if SPD_ScriptPostLoad then
print("SPD (user script "..SPD_scriptnum.."): Warning: Function SPD_ScriptPostLoad already exists")
end
-- backup the normal ScriptPostLoad function
SPD_ScriptPostLoad = ScriptPostLoad

-- redefine ScriptPostLoad to something new
ScriptPostLoad = function()
-- run the mission lua ScriptPostLoad
SPD_ScriptPostLoad()

-- only do these changes when in SP
if not ScriptCB_InMultiplayer() then

-- ==== Code Injected At End ====

-- maybe not a perfect 1 in 2 chance but close enough
if SPD_modeSelect == 1 then

-- keep track of the hero class numbers
local heroClassNum1 = nil
local heroClassNum2 = nil

-- spawn AI heroes for the first time
heroClassNum1 = SPD_SpawnAIHero(1)
heroClassNum2 = SPD_SpawnAIHero(2)

-- respawn AI heroes when they die
OnCharacterDeath(
function(player, killer)
local team = GetCharacterTeam(player)

-- filters
if team == 1 then -- team 1
if GetCharacterClass(player) == heroClassNum1 then -- team 1 hero

-- respawn the AI hero for team 1
SPD_RespawnAIHero(1, heroClassNum1)
end

-- filters
elseif team == 2 then -- team 2
if GetCharacterClass(player) == heroClassNum2 then -- team 2 hero

-- respawn the AI hero for team 2
SPD_RespawnAIHero(2, heroClassNum2)
end
end
end
)

-- EnableSPScriptedHeroes()
-- print("SPD (user script "..SPD_scriptnum.."): EnableSPScriptedHeroes() used")

end

-- ==============================

end
end

else
print("SPD (user script "..SPD_scriptnum.."): Error: Unable to find ScriptPostLoad()")
end

-- ======================================


-- ========================================================
-- ========================================================
[/code]
This also doesn't work. I can get 'EnableSPHeroRules prevented' to come up in the debug log of BF2ModTools.exe but the game's acting like it doesn't need EnableSPHeroRules() and the heroes are still unlockable to the player (even though I'm pretty sure leaving this line out of the mission lua disables hero unlocks). Does anybody know how to use the other Hero lua like SetHeroUnlockRule or SetHeroRespawnRule? If so, I might be able to rescript the hero unlock rules with EnableSPScriptedHeroes() turned on but I don't know the arguments for these lua commands. Or can anybody think of an alternative way to turn heroes off (which doesn't involve disabling SetHeroClass either; I need that for the AI heroes to work)?

Most of this problem involves going several missions deep into an instant action playlist so I can't use BF2ModTools.exe to debug that much.
Hidden/Spoiler:
In case people want to adapt the hero spawning code and use it. It's all based on the Utapau campaign, Zerted's user script example, the modtools documents and stuff I've learned by plugging things into print(). I also wouldn't have attempted an AI hero user script if I hadn't read about archer's hero script on these forums and thought "oh so it's possible."
Edit: I have made a new thread about the problem with EnableSPScriptedHeroes(). If anyone wants to follow that they can find it here. It's not a replacement for this thread, because I'm mostly looking for ideas on how to turn heroes on and off in my user script without using EnableSPScriptedHeroes() here. Or, if anyone knows how to script heroes with the other lua that would be helpful too. The new thread on the other hand, is about lua breaking instant action playlists. Whereas this thread you're in now is about anything and everything that might get this user script to work.

Edit 2: Solution

In the end what I've done is keep EnableSPScriptedHeroes() in, but make a function to recreate some of the instant action hero rules. I haven't got it set up for points but here is a scripted hero spawn with a 60 second unlock timer (and 90 second timer for the 2nd, 3rd, 4th... unlocks):

Code: Select all

-- ========== SPD_HumanHeroSetup(flag) ==========
-- function to spoof some instant action hero rules
-- necessary for compatability with EnableSPScriptedHeroes()
-- locks human heroes when flag == 0

-- standard warning message
if SPD_HumanHeroSetup then
	print("SPD (user script "..SPD_scriptnum.."): Warning: Function SPD_HumanHeroSetup already exists")
end

function SPD_HumanHeroSetup(flag)
	EnableSPScriptedHeroes()
	
	if flag == 1 then
		-- hero first unlocked after a 60 second timer
		CreateTimer("HumanHeroUnlock")
		SetTimerValue("HumanHeroUnlock", 60)
		StartTimer("HumanHeroUnlock")
		OnTimerElapse(
			function (timer)
				-- unlock both heroes
				for team = 1, 2 do
					SetHeroPlayerRule("Best")
					UnlockHeroForTeam(team)
					-- 90 second hero respawn for humans
					SetHeroRespawnRule("90")
				end
				DestroyTimer(timer)
			end,
			"HumanHeroUnlock"
		)
	end
end

-- ==============================================
This is designed so I can turn human hero unlocks off for some missions with SPD_HumanHeroSetup(0) and turn them on with SPD_HumanHeroSetup(1). It's not so robust that you can turn it on then off in the same mission, but it's good enough for what I'm doing. Totally ignores the instant action settings too, unfortunately. Discovered how to use SetHeroRespawnRule and SetHeroPlayerRule from an old gametoast post. Still no luck with SetHeroUnlockRule though.
Last edited by Sporadia on Mon Apr 27, 2020 1:37 pm, edited 13 times in total.
MileHighGuy
Jedi
Jedi
Posts: 1194
Joined: Fri Dec 19, 2008 7:58 pm

Re: Trouble with EnableSPScriptedHeroes() in a user script

Post by MileHighGuy »

Out of curiosity, why are you overriding ScriptPostLoad ? Why not just call your function inside the regular ScriptPostLoad() function?
Sporadia
Corporal
Corporal
Posts: 151
Joined: Thu Jan 24, 2019 11:02 pm
Projects :: No Mod project currently
Games I'm Playing :: None
xbox live or psn: No gamertag set

Re: Trouble with EnableSPScriptedHeroes() in a user script

Post by Sporadia »

MileHighGuy wrote:Out of curiosity, why are you overriding ScriptPostLoad? Why not just call your function inside the regular ScriptPostLoad() function?
This is a user script so it's not the same as writing a mission lua. User scripts are performed somewhere near the beginning of ScriptInit() I think, around the line ReadDataFile("ingame.lvl"). The point of a user script is to write something once and be able to apply it to any mission you load, without having to mod the actual mission. For something like playing with hero rules, it's a lot more versatile to use a user script than if I tried to mod every gametype of every map, every time I wanted to add AI heroes. And to do different modes of AI hero without a user script, I would have to make a version of each mission lua for each mode on their own, as well as an extra one that works with every mode and selects one randomly. And I would have to replace full mod folders every time to change how I wanted to play. And as soon as I want to add new maps or eras to my game, I would have to repeat the whole thing for all the mission lua in that mod. Instead, I'm trying to make something that works automatically on nearly every mission I throw at it so that's why I'm using a user script.

If I wanted to add code into the front of a mission's ScriptInit() I can just write that code out normally because that's where the user script runs. But I can't remove lines from the mission lua with a user script, only add them in. And, the only place I can add them in is the beginning of ScriptInit(). So most functions that are in the mission lua need to be hacked if I want to alter them or disable them, including ScriptPostLoad(). I can't add lines to ScriptPostLoad() normally; the user script doesn't have access to that part of the mission lua. Hacking extra lines in this way works. It's a trick I've stolen from Zerted's Custom User Script how to.
Hidden/Spoiler:
And all of (what I previously believed to be) the hard stuff works like getting heroes to spawn and respawn (and in the more complicated version of the script: creating and destroying all the timers so it doesn't crash the playlists, because that's a thing that happens when you don't destroy all your timers). Even turning AI heroes on and off randomly works in both scripts. I'm just not sure why the command EnableSPScriptedHeroes() is being carried forward from mission to mission (permanent after it's first use) when all of the lua around it isn't. If that's even what's happening, and it's not just that EnableSPScriptedHeroes() changes the settings of instant action. I don't think it does though because people would have noticed when they play campaign missions on instant action if it breaks the hero unlock of the rest of the playlist.

Edit: Ok I've now tested this without any user scripts or mods. I've just played the vanilla game where the only mod on it was Zerted's patch (which is always there). I did the following playlist: Kamino CW Conquest, Geonosis Campaign, Jabba's Palace CW Conquest. On Kamino, heroes in the mission work fine. On Geonosis, the mission worked fine. Then on Jabba's Palace, the heroes weren't unlockable. The exact same problem exists without any mods or user scripts or anything. There's something in the Geonosis Campaign lua which breaks hero unlocks in instant action. At the moment I think it's the line EnableSPScriptedHeroes(), because that would line up with my user script test. I can't think of a proper way to test it at the moment. The only thing closer to a vanilla test is if I removed Zerted's patch and I'm not about do that. I think I might mod a normal conquest mission that I know works, where the only change I make is adding the line EnableSPScriptedHeroes() and see if that breaks instant action too. But it looks at the moment like the actual game has a bug here.

Edit 2: I have made a new thread about the problem with EnableSPScriptedHeroes(). If anyone wants to follow that they can find it here. I'd like this thread you're in now to continue with the problem of finding other ways to turn heroes on and off in my user script, because I still haven't figured out how to do it. The new thread on the other hand is exclusively about lua that breaks instant action playlists (basically there's an underlying problem with EnableSPScriptedHeroes() which doesn't really fix what I'm trying to do so I've started a new thread for that topic specifically; getting this user script to work is my priority here). If you want to see more about this post's first edit, follow it up in the new thread. If you can help with heroes, please tell me here.
Edit 3: Hidden the less relevant stuff to make this shorter.
Post Reply