Page 1 of 1

Remote Droid that detonates on impact

Posted: Wed Apr 14, 2021 1:44 pm
by Benoz
As the title says I am trying to create a remote droid that detonates upon impact with an enemy AI. Unfortunately ExplosionImpact doesn't work on Droid classes and I couldn't figure out any other way, so I tried to achieve it with Lua scripting. That's the code I created:

Code: Select all

nikitaUnit = nil
print("nikitaUnit:", nikitaUnit)

CreateTimer("NikitaPosUpdate")
SetTimerValue("NikitaPosUpdate", 1)

OnCharacterDispenseControllable(
	function(player, controllable)
		if GetEntityClass(controllable) == FindEntityClass("rep_weap_inf_remotedroid_ord") then
			nikitaUnit = controllable
			print("nikitaUnit:", nikitaUnit)
			
			StartTimer("NikitaPosUpdate")
		end
	end
)

OnTimerElapse(
	function(timer)
		if nikitaUnit then
			print("nikitaUnit:", nikitaUnit)
		
			nikitaX,nikitaY,nikitaZ = GetWorldPosition(nikitaUnit)
			print("Nikita X: ", nikitaX)
			print("Nikita Y: ", nikitaY)
			print("Nikita Z: ", nikitaZ)
			
			local teamSize = GetTeamSize(2)
			
			for i = 0, teamSize - 1 do
				enemyIndex = GetTeamMember(2,i)
				print("Getting Position of Enemy ", enemyIndex)
				enemyX,enemyY,enemyZ = GetWorldPosition(GetCharacterUnit(enemyIndex))
				print("Enemy X: ", enemyX)
				print("Enemy Y: ", enemyY)
				print("Enemy Z: ", enemyZ)
				
				if (math.abs(enemyX - nikitaX) <= 32) and (math.abs(enemyY - nikitaZ) <= 32) then
					print("Collide with Enemy ", enemyIndex)
					KillObject(nikitaUnit)
				end
			end
		end 
		
		SetTimerValue("NikitaPosUpdate", 1)
		StartTimer("NikitaPosUpdate")
	end,
	"NikitaPosUpdate"
)
Explanation
Once the player uses the remote droid, a timer is started. if the remote droid is alive, the function fetches its X,Y,Z coordinates and the X,Y,Z coordinates of every enemy AI. Then it checks for every enemy if the distance between the remote droid and that enemy is below a certain number. If this is true, the remote droid explodes. If this is false, the timer starts anew.

Problem
Everything works fine (as far as I can monitor it with the print commands) up until the point where the remote droid is near an enemy. The math statement is not being executed and I don't get a print entry in the BF2Log. And once the droid gets destroyed through time or an enemy shot, the game crashes.

Does somebody know how to prevent the CTD and make sure the math statement is being checked/executed correctly?

Re: Remote Droid that detonates on impact

Posted: Wed Apr 14, 2021 1:54 pm
by Sporadia
Is it a mistake that you're comparing enemyY with nikitaZ?

This is a really simple suggestion that probably won't be it, but I faintly remember having trouble with commands like if math.random(1, 6) == 1 then which I think I solved by doing the math.random outside of the if statement. It wasn't a crash though. What happens if you do the math.abs before you do the if?
eg.

Code: Select all

local xDifference = math.abs(enemyX - nikitaX)
local yDifference = math.abs(enemyY - nikitaY)
local zDifference = math.abs(enemyZ - nikitaZ)
if (xDifference <= 32) and (yDifference <= 32) and (zDifference <= 32) then
	print("Collide with Enemy ", enemyIndex)
	KillObject(nikitaUnit)
end

Re: Remote Droid that detonates on impact

Posted: Wed Apr 14, 2021 2:21 pm
by Benoz
Awesome, that did the trick! Now the math statement gets executed and I get a response.

BF2Log.txt
Hidden/Spoiler:
nikitaUnit: userdata: 07E37CC0
Nikita X: -164.08102416992
Nikita Y: 1.1989616155624
Nikita Z: 199.51429748535
Getting Position of Enemy 5
Enemy X: -93.561325073242
Enemy Y: 0.98000001907349
Enemy Z: 142.15086364746
Getting Position of Enemy 6
Enemy X: -73.01343536377
Enemy Y: 0.98040544986725
Enemy Z: 100.40016174316
Getting Position of Enemy 7
Enemy X: -127.0439453125
Enemy Y: 0.98101043701172
Enemy Z: 178.62501525879
Getting Position of Enemy 8
Enemy X: -69.382301330566
Enemy Y: 0.98003792762756
Enemy Z: 98.895523071289
Getting Position of Enemy 9
Enemy X: -94.973983764648
Enemy Y: 0.98000001907349
Enemy Z: 148.33338928223
Getting Position of Enemy 10
Enemy X: -77.387153625488
Enemy Y: 0.99567884206772
Enemy Z: 90.933670043945
Getting Position of Enemy 11
Enemy X: -91.799133300781
Enemy Y: 0.98000001907349
Enemy Z: 147.32833862305
Getting Position of Enemy 12
Enemy X: -169.74723815918
Enemy Y: 0.98040544986725
Enemy Z: 203.3201751709
Getting Position of Enemy 13
Enemy X: -77.210525512695
Enemy Y: 0.98101043701172
Enemy Z: 98.194877624512
Getting Position of Enemy 14
Enemy X: -67.164535522461
Enemy Y: 0.98016083240509
Enemy Z: 108.30587005615
Getting Position of Enemy 15
Enemy X: -163.95088195801
Enemy Y: 0.98003792762756
Enemy Z: 201.88360595703
Collide with Enemy 15

But unfortunately when the object gets killed, the game still crashes. Could it be because the kill command is in the function that checks if the remote droid is alive? (if nikitaUnit then)

EDIT: Nope, it still crashes when I move the killObject outside the function and initiate it with a timer. My second guess would be the for loop as it continues to run even after the collision happened and the remote droid is destroyed.

New BF2Log.txt
Hidden/Spoiler:
nikitaUnit: userdata: 07FF1C40
Nikita X: -107.66374206543
Nikita Y: 1.1990495920181
Nikita Z: 136.84364318848
Getting Position of Enemy 5
Enemy X: -175.91766357422
Enemy Y: 0.99538546800613
Enemy Z: 179.9130859375
Getting Position of Enemy 6
Enemy X: -85.346557617188
Enemy Y: 0.99532562494278
Enemy Z: 91.383529663086
Getting Position of Enemy 7
Enemy X: -173.42977905273
Enemy Y: 0.99562096595764
Enemy Z: 193.52914428711
Getting Position of Enemy 8
Enemy X: -77.729270935059
Enemy Y: 0.99573642015457
Enemy Z: 89.408149719238
Getting Position of Enemy 9
Enemy X: -108.67774963379
Enemy Y: 0.99544489383698
Enemy Z: 138.40371704102
Collide with Enemy 9
Getting Position of Enemy 10
Enemy X: -105.54786682129
Enemy Y: 0.99508261680603
Enemy Z: 144.50973510742
Getting Position of Enemy 11
Enemy X: -137.38536071777
Enemy Y: 0.99550396203995
Enemy Z: 177.45980834961
Getting Position of Enemy 12
Enemy X: -134.85348510742
Enemy Y: 0.99062728881836
Enemy Z: 173.28887939453
Getting Position of Enemy 13
Enemy X: -68.723876953125
Enemy Y: 0.98000001907349
Enemy Z: 97.07315826416
Getting Position of Enemy 14
Enemy X: -178.24624633789
Enemy Y: 0.98000001907349
Enemy Z: 178.82249450684
Getting Position of Enemy 15
Enemy X: -178.62768554688
Enemy Y: 0.99556261301041
Enemy Z: 190.33854675293
Getting Position of Enemy 16
Enemy X: -178.27635192871
Enemy Y: 0.98000001907349
Enemy Z: 184.02867126465
nikitaUnit: userdata: 07FF1C40

Re: Remote Droid that detonates on impact

Posted: Wed Apr 14, 2021 2:35 pm
by Sporadia
I see another mistake. You're not actually checking if nikita is alive when the timer elapses. nikitaUnit is a copy of the objectData that you made during the OnCharacterDispenseControllable event; it won't update itself. So you're checking nikitaUnit when the timer elapses, but what you're actually checking is if nikita was alive immediately after the controllable was dispensed, because that's when you copied the objectData into nikitaUnit.

You should probably use nikitaPtr as a global variable instead, found with GetObjectPtr(controllable). Then when the timer elapses, you'd somehow need to use the nikitaPtr to update the nikitaUnit. I don't know what functions go backwards from the objectPtr to the objectData, but that seems like the way to do it.

Edit: I wonder if IsObjectAlive() uses the objectPtr. Then you could get nikitaUnit and nikitaPtr in the OnCharacterDispenseCollectable. And then you'd begin OnTimerElapse by checking IsObjectAlive(nikitaPtr) to make sure that the nikitaUnit is still usable. Doing stuff like GetWorldPosition(nikitaUnit) on a dead nikita might be the source of the crash.

eg

Code: Select all

nikitaUnit = nil
nikitaPtr = nil

OnCharacterDispenseControllable(
	function(player, controllable)
		if GetEntityClass(controllable) == FindEntityClass("rep_weap_inf_remotedroid_ord") then
			nikitaUnit = controllable
			nikitaPtr = GetObjectPtr(controllable)
			
			StartTimer("NikitaPosUpdate")
		end
	end
)

OnTimerElapse(
	function(timer)
		if nikitaUnit and nikitaPtr then -- error check
			if IsObjectAlive(nikitaPtr) then -- check that the object is alive
			
				-- code in here should be able to use nikitaUnit with no problems
				
			end
		end
		
		SetTimerValue("NikitaPosUpdate", 1)
		StartTimer("NikitaPosUpdate")
	end,
	"NikitaPosUpdate"
)
Also make sure you have some way of destroying the timer through code. I've suspected for a while that timers crash instant action playlists when you don't destroy them because the game doesn't clean them up properly after the mission. But I've haven't really tested the idea; it's just something that's given me problems before.

edit2: You're probably right that the for loop is a problem too. But you can just add a break after the KillObject to stop it looping.

Re: Remote Droid that detonates on impact

Posted: Wed Apr 14, 2021 5:08 pm
by Benoz
Thanks for the input Sporadia. I redid some parts of the code and added a variable that checks if a collision is happening or not. But I think the important part was the Ptr and the break of the for loop.

This is the code I am using and it works with several remote droids in a row.

Code: Select all

nikitaUnit = nil
nikitaPtr = nil
nikitaCollision = false

CreateTimer("NikitaPosUpdate")
SetTimerValue("NikitaPosUpdate", 1)

CreateTimer("NikitaRefresh")
SetTimerValue("NikitaRefresh", 1.1)

OnCharacterDispenseControllable(
   function(player, controllable)
		if GetEntityClass(controllable) == FindEntityClass("rep_weap_inf_remotedroid_ord") then
			nikitaUnit = controllable
			nikitaPtr = GetObjectPtr(controllable)
			print("MGS_Rocket_Script: nikitaPtr:", nikitaPtr)
			StartTimer("NikitaPosUpdate")
		end
	end
)

OnTimerElapse(
	function(timer)
		if nikitaUnit and nikitaPtr then
			if IsObjectAlive(nikitaPtr) then
				print("MGS_Rocket_Script: nikitaUnit:", nikitaUnit)
				print("MGS_Rocket_Script: nikitaPtr:", nikitaPtr)
			
				nikitaX,nikitaY,nikitaZ = GetWorldPosition(nikitaUnit)
				print("MGS_Rocket_Script: Nikita X ", nikitaX)
				print("MGS_Rocket_Script: Nikita Z ", nikitaZ)
				
				local teamSize = GetTeamSize(2)
				
				for i = 0, teamSize - 1 do
					enemyIndex = GetTeamMember(2,i)
					print("MGS_Rocket_Script: Getting Position of Enemy ", enemyIndex)
					enemyX,enemyY,enemyZ = GetWorldPosition(GetCharacterUnit(enemyIndex))
					print("MGS_Rocket_Script: Enemy X ", enemyX)
					print("MGS_Rocket_Script: Enemy Z ", enemyZ)
					
					local xDifference = math.abs(enemyX - nikitaX)
					local zDifference = math.abs(enemyZ - nikitaZ)
					
					if (xDifference <= 2) and (zDifference <= 2) then
						print("MGS_Rocket_Script: Collide with Enemy ", enemyIndex)
						nikitaCollision = true
						KillObject(nikitaUnit)
						StartTimer("NikitaRefresh")
						break
					end
				end
			end
		end 
		
		if nikitaCollision == false then
			SetTimerValue("NikitaPosUpdate", 1)
			StartTimer("NikitaPosUpdate")
			print("MGS_Rocket_Script: Restart NikitaPosUpdate Timer")
		end
	end,
	"NikitaPosUpdate"
)

OnTimerElapse(
	function(timer)
		print("MGS_Rocket_Script: Restart Nikita script")
		nikitaCollision = false
	end,
	"NikitaRefresh"
)

Sometimes, although seldom, the droid doesn't explode upon collision and is therefore stuck. It doesn't crash anymore, but I get this error:

Hidden/Spoiler:
...
MGS_Rocket_Script: Getting Position of Enemy 6
MGS_Rocket_Script: Enemy X -112.08446502686
MGS_Rocket_Script: Enemy Z 144.57516479492
MGS_Rocket_Script: Collide with Enemy 6
MGS_Rocket_Script: Restart Nikita script
MGS_Rocket_Script: nikitaPtr: userdata: 07DE0CC0
MGS_Rocket_Script: nikitaUnit: userdata: 07DE0CC0
MGS_Rocket_Script: nikitaPtr: userdata: 07DE0CC0
MGS_Rocket_Script: Nikita X -211.57345581055
MGS_Rocket_Script: Nikita Z 131.76643371582
MGS_Rocket_Script: Getting Position of Enemy 5
MGS_Rocket_Script: Enemy X -122.76979827881
MGS_Rocket_Script: Enemy Z 140.19921875
MGS_Rocket_Script: Getting Position of Enemy 6
MGS_Rocket_Script: Enemy X -120.69888305664
MGS_Rocket_Script: Enemy Z 139.28523254395
MGS_Rocket_Script: Getting Position of Enemy 7

Message Severity: 3
C:\Battlefront2\main\Battlefront2\Source\LuaHelper.cpp(312)
CallProc failed: bad argument #1 to `GetWorldPosition' (string expected, got nil)
stack traceback:
[C]: in function `GetWorldPosition'
(none): in function <(none):69>

Usually the for loop stops right at the red marked line where the collision happens, but here it goes on. I think this has something to do with the relatively long timer intervall of 1 sec

Re: Remote Droid that detonates on impact

Posted: Wed Apr 14, 2021 7:42 pm
by AnthonyBF2
You could just edit the com_weap_inf_remotedroid_destruct.ODF and change MaxPressedTime to 0.1 so it explodes as soon as you hit the trigger.