A Torpedo Attacker Script

This is a tutorial of how to make a torpedo attacker that carries a torpedo, fire it to the enemy, then head back and dock, carries a new torpedo.

What you should prepare:

  1. A ship that supposed to be the attacker
  • Add CustomCode into its .ship file ([FunctionCreate],[FunctionUpdate],[FunctionDestroy],0.5)

  • Add NewShipType.paradeData into its .ship file, this determines where the torpedo will appear when launched

  1. A torpedo which is actually a ship
  • Make it attacks everything using Kamikaze attackstyle

  • Edit NewShipType.collisionMultiplier to set a proper damage

  1. An effect which use the same hod of the torpedo ([TorpedoEffectName])

Add following script into this ship’s CustomCode:

Custom Code
function [FunctionCreate](CustomGroup, playerIndex, shipID)
	SobGroup_CreateIfNotExist("TorpedoAttacker"..shipID)
	SobGroup_CreateIfNotExist("TorpedoAttackerTargetTempGroup")
	SobGroup_CreateIfNotExist("TorpedoAttackerTargetTempGroup2")
	SobGroup_CreateIfNotExist("TorpedoTempGroup")
end

function [FunctionUpdate](CustomGroup, playerIndex, shipID)
	if(SobGroup_Empty("TorpedoAttacker"..shipID)==1)then
		FX_StartEvent(CustomGroup, [TorpedoEffectName])--an effect that displayed on the ship so that it looks like the ship carries a torpedo, replace [TorpedoEffectName] by your effect name
		SobGroup_SobGroupAdd("TorpedoAttacker"..shipID,CustomGroup)
	elseif(SobGroup_CanDoAbility(CustomGroup, AB_Attack)==1)then
		if(SobGroup_GetCurrentOrder(CustomGroup)==COMMAND_Attack)then
			SobGroup_Clear("TorpedoAttackerTargetTempGroup")
			SobGroup_GetCommandTargets("TorpedoAttackerTargetTempGroup", CustomGroup, COMMAND_Attack)
			if(SobGroup_Count("TorpedoAttackerTargetTempGroup")==1)then
				if(SobGroup_AreAnyFromTheseAttackFamilies("TorpedoAttackerTargetTempGroup", "Capturer, Frigate, SmallCapitalShip, BigCapitalShip, Mothership, Utility")==1)then--edit the attack families the attacker can attack, or simply copy this from the attacker's .ship file
					SobGroup_Clear("TorpedoAttackerTargetTempGroup2")
					Player_FillProximitySobGroup("TorpedoAttackerTargetTempGroup2", playerIndex, "TorpedoAttackerTargetTempGroup", 2500)--edit the firerange of the torpedo, how far the attack will fire upon the target
					if(SobGroup_GroupInGroup("TorpedoAttackerTargetTempGroup2",CustomGroup)==1)then
						SobGroup_Clear("TorpedoTempGroup")
						Volume_AddSphere("AttackerPositionVolume", SobGroup_GetPosition(CustomGroup), 0)
						SobGroup_SpawnNewShipInSobGroup(playerIndex, [TorpedoName], [TorpedoName]..shipID, ”TorpedoTempGroup", "AttackerPositionVolume")--replace [TorpedoName] by your torpedo unit's ship name
						Volume_Delete("AttackerPositionVolume")
						SobGroup_ParadeSobGroup("TorpedoTempGroup", CustomGroup, 2)
						SobGroup_Kamikaze("TorpedoTempGroup", "TorpedoAttackerTargetTempGroup")
						SobGroup_AbilityActivate(CustomGroup, AB_Attack, 0)
						SobGroup_DockSobGroup(CustomGroup, "Player_Ships"..playerIndex)
						FX_StopEvent(CustomGroup, [TorpedoEffectName])--stop playing the effect, so that the torpedo carried disappears, replace  by your effect name
						FX_StartEvent(CustomGroup, [FireEffectName])--play a firing effect on the ship, replace [FireEffectName] by your effect name
					end
				end
			end
		end
	elseif(SobGroup_IsDocked(CustomGroup)==1)then
		FX_StartEvent(CustomGroup, [TorpedoEffectName])--ship is docked, carry a new torpedo, replace [TorpedoEffectName] by your effect name
		SobGroup_AbilityActivate(CustomGroup, AB_Attack, 1)
	end
end

function [FunctionDestroy](CustomGroup, playerIndex, shipID)
	if(SobGroup_CanDoAbility(CustomGroup, AB_Attack)==1)then
		FX_StartEvent(CustomGroup, [DeathEffectWithTorpedo])--ship is dead with the torpedo, so a bigger death effect should be played, replace [DeathEffectWithTorpedo] by your effect name
	else
		FX_StartEvent(CustomGroup, [DeathEffectWithoutTorpedo])--ship is dead without the torpedo, so a normal death effect should be played, replace [DeathEffectWithoutTorpedo] by your effect name
	end
end

EDIT: I wrote a better code for single-shot missile that uses actual missile instead of kamikaze unit. See here.

7 Likes

Thanks for sharing this! Added a link from the tutorial thread.

1 Like

An interesting take on the ammunition issue, and one that, I suppose, could be tinkered with to give a definted number of torpedoes to the ship before reloading (by adding a subsystem health based counter as well as a timer*). If one wanted to have the torpedo to be an actual missile rather than a kamikaze ship (which, by the way, could be combined with the cloaking missile script I had), it would be pretty easy to make the “ship-torpedo” fire a single shot with a missile weapon before self-destroying.

  • get a subsystem that loses, say, 1 % of its health every cycle and the whole script can fire only when that subsystem is at 0 health, but the subsystem is entirely repaired when the script shoots a torpedo. Then, it would remove 1/n health of the ammunition counter subsystem, n being the torpedo count you want for each ship. When the latter subs gets to 0 health, you can activate an automated RTB script, preferentially like the one I’m using which checks for closeby carriers before trying to land on a random allied ship.

That’s pretty smart indeed: if we cannot detect the moment a ship shoots its weapon, then let’s simulate a new weapon through Custom Code. I tip my hat to you, @HW_Lover, you thought outside the box in a way I couldn’t.

3 Likes

I’ve just thought about something that might be stupid, but I need some feedback:

Why exactly don’t we make all missiles like this? OK, that would of course require an initial coding job to do it, but the more I think about it, the more advantages I’m seeing. The whole “visible torpedo” on the outside of the ship is an awesome option indeed, but we could push the whole thing further:

We have our missile-armed craft, using global tables like described in this thread: [QUESTION] Detecting an animation or madstate

Now, this table management system can get ammunition counts, of course, but also reload time for the weapon, and we have range from the OP: the base for a weapon.

A CustomCode I derived from my current take on fuel management thanks to various other people here, plus the global table management linked above and the OP's idea - code not yet tested, I've been too busy recently

dofilepath(“data:scripts/lib/custom/shiptable.lua”)

function OnCreate(CustomGroup, playerID, shipID)
dofilepath(“data:scripts/lib/custom/shiptable.lua”)

if ships==nil then
print(“Table créée.”)
ships = {}
fuel = {}
fuel.fuel = 100
fuel.fuel_max = 100
weapon = {}
weapon.subsname = "myweapon"
weapon.ammo = 2
weapon.ammo_max = 2
weapon.ammo_use = 1 – per shot
weapon.reload = 0
weapon.reload_max = 100
weapon.recharge = 1 – per update (used to define whether we are allowed to fire at all, simulates rate of fire)
else
print(“Table trouvée.”)
end

ships.weapon = weapon
print(shipID)
a = shipID
print(a)
ships[a] = ships
print(ships)

end

function OnUpdate(CustomGroup, playerID, shipID)
dofilepath(“data:scripts/lib/custom/shiptable.lua”)
b = shipID
print(ships)
print(b)
print(ships[b].weapon.ammo)

c = ships[b].fuel.fuel
d = ships[b].weapon.ammo
print(“Munitions pour vaisseau :”)
print(d)

e = ships[b].weapon.reload
print(“Cycle de rechargement pour vaisseau :”)
print(e)

if c>0 then
ships[b].fuel.fuel = c-1
print(“Carburant pour vaisseau :”)
print©
RTB = 0
else
RTB = 1
end

if d>0 and e==ships[b].weapon.reload_max then
	if(SobGroup_GetCurrentOrder(CustomGroup)==COMMAND_Attack)then
		SobGroup_Clear("TorpedoAttackerTargetTempGroup")
		SobGroup_GetCommandTargets("TorpedoAttackerTargetTempGroup", CustomGroup, COMMAND_Attack)
		if(SobGroup_Count("TorpedoAttackerTargetTempGroup")==1)then
			if(SobGroup_AreAnyFromTheseAttackFamilies("TorpedoAttackerTargetTempGroup", "Capturer, Frigate, SmallCapitalShip, BigCapitalShip, Mothership, Utility")==1)then--edit the attack families the attacker can attack, or simply copy this from the attacker's .ship file
				SobGroup_Clear("TorpedoAttackerTargetTempGroup2")
				Player_FillProximitySobGroup("TorpedoAttackerTargetTempGroup2", playerIndex, "TorpedoAttackerTargetTempGroup", 4500)--edit the firerange of the torpedo, how far the attack will fire upon the target
				if(SobGroup_GroupInGroup("TorpedoAttackerTargetTempGroup2",CustomGroup)==1)then
					SobGroup_Clear("TorpedoTempGroup")
					Volume_AddSphere("AttackerPositionVolume", SobGroup_GetPosition(CustomGroup), 0)
					SobGroup_SpawnNewShipInSobGroup(playerIndex, "flg_torpedo_1", "flg_torpedo_1"..shipID, "TorpedoTempGroup", "AttackerPositionVolume")--replace [TorpedoName] by your torpedo unit's ship name
					Volume_Delete("AttackerPositionVolume")
					SobGroup_ParadeSobGroup("TorpedoTempGroup", CustomGroup, 2)
					SobGroup_Attack(PlayerIndex, "TorpedoTempGroup", "TorpedoAttackerTargetTempGroup")
					--SobGroup_Kamikaze("TorpedoTempGroup", "TorpedoAttackerTargetTempGroup")


						ships[b].weapon.ammo = c-1
						RTB = 0
						else
						RTB = 1
						
					SobGroup_SetHealth("TorpedoTempGroup", 0)
				end
			end
		end
	end
end

if RTB == 1 then

SobGroup_FillProximitySobGroup("Test1", "Player_Ships"..playerID, CustomGroup, 1500)
SobGroup_FillProximitySobGroup("OwnShip", "Player_Ships"..playerID, CustomGroup, 5)
SobGroup_FillSubstract("Test2", "Test1", "OwnShip")
			
	if SobGroup_AreAnyOfTheseTypes("Test2", "cfd_carrier, cfd_confederation, cfd_jutland, cfd_lexington, cfd_mothership, cfd_naval_base, cfd_savannah, cfd_victory, klr_bhantkara, klr_dubav, klr_hvarkann, klr_shipyard, klr_starbase, ubw_carrier, ubw_mothership, ubw_shipyard")==1 then -- there's a carrier in proximity, let's not go all over the map for nothing, shall we?
		print("CV found near me.")
		SobGroup_FillShipsByType("Test2", "cfd_carrier, cfd_confederation, cfd_jutland, cfd_lexington, cfd_mothership, cfd_naval_base, cfd_savannah, cfd_victory, klr_bhantkara, klr_dubav, klr_hvarkann, klr_shipyard, klr_starbase, ubw_carrier, ubw_mothership, ubw_shipyard")
		if SobGroup_IsDoingAbility("Test2", AB_Dock)==1 then
		print("No Priority")
			SobGroup_GuardSobGroup(CustomGroup, "Carrier") -- there are other ships waiting to land around me, I'll guard my carrier instead of sitting like a target			
	else
		print("Priority") -- looks like noone is waiting to land, the sky is empty or everyone else is on guard, so it's my turn to land
		SobGroup_DockSobGroup(CustomGroup, "Carrier")													
	end
	else
		print("No CV found around me.") -- no carrier around, I'll land on a random friendly carrier
		SobGroup_DockSobGroup(CustomGroup, "AllShips")
	end

SobGroup_SetHardPointHealth(CustomGroup,‘TORPEDO’,c/ships[b].fuel.fuel_max)
SobGroup_SetHardPointHealth(CustomGroup,‘FUELTANK’,d/ships[b].weapon.ammo_max)

end

if SobGroup_IsDocked(CustomGroup)==1 then

ships[b].weapon.ammo = weapon.ammo_max – I got my ammunition reloaded by the ground crews
ships[b].fuel.fuel = fuel.fuel_max – I got my fuel reloaded by the ground crews

end

end

if SobGroup_GetHardPointHealth(CustomGroup,‘FUELTANK’)>0.005 then
SobGroup_SetHardPointHealth(CustomGroup,‘FUELTANK’,SobGroup_GetHardPointHealth(CustomGroup,‘FUELTANK’)-(0.001))
end

–Check health of weapon subsytem.
if SobGroup_GetHardPointHealth(CustomGroup,‘TORPEDO’) < 1 then – the weapon fired

SobGroup_SetHardPointHealth(CustomGroup,‘TORPEDO’,1)
ships[b].weapon.ammo = ships[b].weapon.ammo - ships[b].weapon.ammo_use – ammunition spent for that shot
print(ships[b].weapon.ammo)

end

if (ships[b].weapon.ammo==0 or SobGroup_GetHardPointHealth(CustomGroup,‘FUELTANK’)<0.05) then – all ammunition expended

SobGroup_FillProximitySobGroup("Test1", "Player_Ships"..playerID, CustomGroup, 1500)
SobGroup_FillProximitySobGroup("OwnShip", "Player_Ships"..playerID, CustomGroup, 5)
SobGroup_FillSubstract("Test2", "Test1", "OwnShip")
			
	if SobGroup_AreAnyOfTheseTypes("Test2", "cfd_carrier, cfd_confederation, cfd_jutland, cfd_lexington, cfd_mothership, cfd_naval_base, cfd_savannah, cfd_victory, klr_bhantkara, klr_dubav, klr_hvarkann, klr_shipyard, klr_starbase, ubw_carrier, ubw_mothership, ubw_shipyard")==1 then -- there's a carrier in proximity, let's not go all over the map for nothing, shall we?
		print("CV found near me.")
		SobGroup_FillShipsByType("Test2", "cfd_carrier, cfd_confederation, cfd_jutland, cfd_lexington, cfd_mothership, cfd_naval_base, cfd_savannah, cfd_victory, klr_bhantkara, klr_dubav, klr_hvarkann, klr_shipyard, klr_starbase, ubw_carrier, ubw_mothership, ubw_shipyard")
		if SobGroup_IsDoingAbility("Test2", AB_Dock)==1 then
		print("No Priority")
			SobGroup_GuardSobGroup(CustomGroup, "Carrier") -- there are other ships waiting to land around me, I'll guard my carrier instead of sitting like a target			
	else
		print("Priority") -- looks like noone is waiting to land, the sky is empty or everyone else is on guard, so it's my turn to land
		SobGroup_DockSobGroup(CustomGroup, "Carrier")													
	end
	else
		print("No CV found around me.") -- no carrier around, I'll land on a random friendly carrier
		SobGroup_DockSobGroup(CustomGroup, "AllShips")
	end

end

if SobGroup_IsDocked(CustomGroup)==1 then

ships[b].weapon.ammo = weapon.ammo_max – I got my ammunition reloaded by the ground crews

end

end

What if we do this for all missiles? Anti-ship, anti-fighters and so on? A .ship based missile could have the kinematics optimized for the missile job, and would maybe not even require a CustomCode of its own to work unless you want to do some quirky thing with it (like the cloaking device). Spawn it with the launcher ship’s CustomCode, get it unselectable, order it to Kamikaze on the target and there we go. This would avoid the huge lag trap that dozens or hundreds of CustomCode ships updating at once would cause (though the main issue would be to stress-test how laggy we’d get with all missile-equipped ships having code like the one I posted, but from what I’ve seen playing Flag Commander in SP or MP, it’s not too much of an issue, not like, say, my ill-thought attempt at generating ship names which made the stuff lag very quickly).

However, we could then start doing quirky things. HWR is not really designed for anti-missile defences, so complete roundabouts had to be made to allow the stuff like area missile defence and the like, so what could we do if, in our hypothetical mod, missiles were .ship rather than .missile?

For example, we could get some smart targeting or multiple independant vehicles, like cluster missiles in which each cluster automatically targets a different ship, or clusters of different warheads, like area of effect bombs, anti-fighter missiles and anti-ship missiles at the same time.

Something even more interesting in my opinion is the possibility to introduce ECM, Electronic Counter-Measures. Detecting and acting on missiles would become a completely different toolbox in this case. You could have, for example, decoys, “missiles” that would actually have a CustomCode defining a short lifetime and looking for enemy units belonging to the SobGroup “AntiFighterMissiles”, choosing one of them and forcefully changing the original target to get the missile to kamikaze on the decoy instead. Or a dedicated ship around which missiles would be forced to swerve and fly away in some “repulsor field” way. Or even take control of enemy missiles and turn them back to their sender if some temporary special weapon is turned on.

SEAD (Suppression of Enemy Air Defences) could be made a lot simpler for mods in which capital ships have individual targetable turrets, with dedicated bombers simply attacking the capital ship and dropping one or several missiles that could be made to directly target subsystems of the target ship.

That’s a lead I think we should think about, maybe not generalizing it to all missiles and limiting it to specific stuff, but the OP opened, IMO, a very interesting door for game mechanics.

3 Likes

I like this idea. Presumably we could also do this to existing missiles, by using the missile HOD as a ship…

1 Like

That’s exactly the idea. Take their HOD or use custom ones and build ships out of them. We could have all the tricks of the trade used to and from missiles.

2 Likes

Question about parade formation, @HW_Lover:

I have these two slots for a VLS grid and I want my missiles to take off vertically and upwards. The second set of coordinates {x, y, z} in each line, from what I understand and tested, is the heading of the ship in parade formation. z allows to me to point towards the front, x to right or left, but with y, I have an issue: I can put it in positive or in negative, it doesn’t matter, my ship will point down in every case. Is there something I missed to get my ship to point up in the parade formation? Thanks!

paradeSlot(“Msl_Spiculim_1”, {3.5, 50, -57.5}, |{0,1,0}, {1,0,0}, 0 );|

paradeSlot(“Msl_Spiculim_2”, {-3.5, 50, -57.5}, |{0,-1,0}, {1,0,0}, 0 );|

With functions from tai_defensefighter.lua, I’ve written a better script that can actually detect whether a unit (need to be a small one, fighter/corvette class) has fired a missile. I’ll leave the code here in case anyone need it.

Better Script
function [FunctionCreate](CustomGroup, playerIndex, shipID)
	SobGroup_CreateIfNotExist("TorpedoAttacker"..shipID)
end

function [FunctionUpdate](CustomGroup, playerIndex, shipID)
	if(SobGroup_Empty("TorpedoAttacker"..shipID)==1)then
		FX_StartEvent(CustomGroup, [TorpedoEffectName])--an effect that displayed on the ship so that it looks like the ship carries a torpedo, replace [TorpedoEffectName] by your effect name
		SobGroup_SobGroupAdd("TorpedoAttacker"..shipID,CustomGroup)
	elseif(SobGroup_CanDoAbility(CustomGroup, AB_Attack)==1)then
	    Selection_Create("AllMissiles")
	    if (Selection_GetMissiles("AllMissiles") > 0) then
			Selection_Create("OwnedMissiles"..playerIndex)
		    if Selection_FilterInclude("OwnedMissiles"..playerIndex, "AllMissiles", "PlayerOwner", ""..playerIndex, "")>0 then
				local LaunchSite = SobGroup_GetPosition(CustomGroup)
				local LaunchSitePos = LaunchSite[1]..","..LaunchSite[2]..","..LaunchSite[3]
			    Selection_Create("SelfTorpedo"..shipID)
			    if (Selection_FilterInclude("SelfTorpedo"..shipID, "OwnedMissiles"..playerIndex, "NearPoint", LaunchSitePos, ""..20)>0)and(SobGroup_IsDoingAbility(CustomGroup, AB_Attack)==1) then
					SobGroup_AbilityActivate(CustomGroup, AB_Attack, 0)
					SobGroup_DockSobGroup(CustomGroup, "Player_Ships"..playerIndex)
					FX_StopEvent(CustomGroup, [TorpedoEffectName])--stop playing the effect, so that the torpedo carried disappears, replace  by your effect name
				end
			end
		end
	elseif(SobGroup_IsDocked(CustomGroup)==1)then
		FX_StartEvent(CustomGroup, [TorpedoEffectName])--ship is docked, carry a new torpedo, replace [TorpedoEffectName] by your effect name
		SobGroup_AbilityActivate(CustomGroup, AB_Attack, 1)
	end
end

X_CustomFunctionDestroy["tur_torpedocorvette"]=function(CustomGroup, playerIndex, shipID)
	if(SobGroup_CanDoAbility(CustomGroup, AB_Attack)==1)then
		FX_StartEvent(CustomGroup, [DeathEffectWithTorpedo])--ship is dead with the torpedo, so a bigger death effect should be played, replace [DeathEffectWithTorpedo] by your effect name
	else
		FX_StartEvent(CustomGroup, [DeathEffectWithoutTorpedo])--ship is dead without the torpedo, so a normal death effect should be played, replace [DeathEffectWithoutTorpedo] by your effect name
	end
end
2 Likes