Hard Light Productions Forums

Modding, Mission Design, and Coding => The Scripting Workshop => Topic started by: krevett62 on January 04, 2017, 07:17:09 am

Title: Handling multiple players
Post by: krevett62 on January 04, 2017, 07:17:09 am
Hello, I'm just a beginner at lua scripting and I try to write a small script for ships special features.

It works wonders in singleplayer, but I wonder how can I make this work in MP.
A quick test in MP shows that each player has is own timer flowing independently of the other players when pressing a binded key, but the effect on their ship does not seem to work. However as I said it works flawlessly in SP.
So my question is how can I handle multiple player ships with lua scripting?

Thanks for your advices!

Edit: I can paste the script if that helps?

Code: [Select]
$On Game Init:
[
cloak_ships = {["Shroud"] = true,["Basilisk"] = true,["Kamov#Stealth"] = true}
spectral_ships = {["Crusader"] = true,["Naginata"] = true,["Shroud"] = true,["Tempest"] = true,["Azan"] = true,["Haidar"] = true}
ecm_ships = {["Grendel"] = true,["Mirage"] = true,["Phoenix"] = true,["Wolverine"] = true,["Kossac"] = true,["Sailin"] = true}
player_cloak_duration = 30
player_spectral_duration = 30
player_ecm_duration = 30
ai_cloak_duration = 20
ai_decloak_duration = 15
ai_spectral_duration = 10
ai_nospectral_duration = 20
ai_ecm_duration = 20
ai_noecm_duration = 15
]

$On Mission Start:
[
cloaktime = nil
decloaktime = nil
ecmtime = nil
noecmtime = nil
spectraltime = nil
nospectraltime = nil
]


$State: GS_STATE_GAME_PLAY
$On Gameplay Start:
[
player = hv.Player

if player:isValid() then
callsign = player.Name
timer = {[callsign .. "_cloakremaining"] = player_cloak_duration,[callsign .. "_spectralremaining"] = player_spectral_duration,[callsign .. "_ecmremaining"] = player_ecm_duration,[callsign .. "_cloaked"] = false,[callsign .. "_cloaktime"] = 0,[callsign .. "_decloaktime"] = 0,[callsign .. "_spectral"] = false,[callsign .. "_spectraltime"] = 0,[callsign .. "_nospectraltime"] = 0,[callsign .. "_ecm"] = false,[callsign .. "_ecmtime"] = 0,[callsign .. "_noecmtime"] = 0}
end
]

$State: GS_STATE_GAME_PLAY
$On Frame:
[
local num = #mn.Ships
local ship
local class
local name
local time
local display

if player:isValid() then
playership = mn.getObjectFromSignature(player:getSignature())
end

for i = 1, num do
ship = mn.Ships[i]

if ship:isValid() and ship ~= playership then
class = ship.Class

if cloak_ships[class.Name] then
name = ship.Name
time = mn.getMissionTime()

if not decloaktime then
decloaktime = ai_cloak_duration
end

if not cloaktime then
cloaktime = ai_cloak_duration + ai_decloak_duration
end

if time <= decloaktime then
mn.runSEXP(" (ship-stealthy !" .. name .. "!) ")

elseif time > decloaktime and time <= cloaktime then
mn.runSEXP(" (ship-unstealthy !" .. name .. "!) ")

else
mn.runSEXP(" (ship-stealthy !" .. name .. "!) ")
cloaktime = cloaktime + ai_cloak_duration + ai_decloak_duration
decloaktime = decloaktime + ai_cloak_duration + ai_decloak_duration
end
end

if spectral_ships[class.Name] then
name = ship.Name
time = mn.getMissionTime()

if not nospectraltime then
nospectraltime = ai_spectral_duration
end

if not spectraltime then
spectraltime = ai_spectral_duration + ai_nospectral_duration
end

if time <= nospectraltime then
mn.runSEXP(" (set-armor-type !" .. name .. "! !True! !SL-Spectral Shield! !Shields!) ")

elseif time > nospectraltime and time <= spectraltime then
mn.runSEXP(" (set-armor-type !" .. name .. "! !True! !SL-Standard Shield! !Shields!) ")

else
mn.runSEXP(" (set-armor-type !" .. name .. "! !True! !SL-Spectral Shield! !Shields!) ")
spectraltime = spectraltime + ai_spectral_duration + ai_nospectral_duration
nospectraltime = nospectraltime + ai_spectral_duration + ai_nospectral_duration
end
end

if ecm_ships[class.Name] then
name = ship.Name
time = mn.getMissionTime()

if not noecmtime then
noecmtime = ai_ecm_duration
end

if not ecmtime then
ecmtime = ai_ecm_duration + ai_noecm_duration
end

if time <= noecmtime then
mn.runSEXP(" (turret-protect-ship !beam! !" .. name .. "!) ")
mn.runSEXP(" (turret-protect-ship !laser! !" .. name .. "!) ")
mn.runSEXP(" (turret-protect-ship !missile! !" .. name .. "!) ")
mn.runSEXP(" (turret-protect-ship !flak! !" .. name .. "!) ")

elseif time > noecmtime and time <= ecmtime then
mn.runSEXP(" (turret-unprotect-ship !beam! !" .. name .. "!) ")
mn.runSEXP(" (turret-unprotect-ship !laser! !" .. name .. "!) ")
mn.runSEXP(" (turret-unprotect-ship !missile! !" .. name .. "!) ")
mn.runSEXP(" (turret-unprotect-ship !flak! !" .. name .. "!) ")

else
mn.runSEXP(" (turret-protect-ship !beam! !" .. name .. "!) ")
mn.runSEXP(" (turret-protect-ship !laser! !" .. name .. "!) ")
mn.runSEXP(" (turret-protect-ship !missile! !" .. name .. "!) ")
mn.runSEXP(" (turret-protect-ship !flak! !" .. name .. "!) ")
ecmtime = ecmtime + ai_ecm_duration + ai_noecm_duration
noecmtime = noecmtime + ai_ecm_duration + ai_noecm_duration
end
end

if class == tb.ShipClasses['Mine'] then
name = ship.Name
mn.runSEXP(" (when (> (distance !" .. name .. "! !<any friendly>!) !500!) (ship-stealthy !" .. name .. "!)) ")
mn.runSEXP(" (when (<= (distance !" .. name .. "! !<any friendly>!) !500!) (ship-unstealthy !" .. name .. "!) (alter-ship-flag !hidden-from-sensors! !True! !False! !" .. name .. "!)) ")
mn.runSEXP(" (when (<= (distance !" .. name .. "! !<any friendly>!) !100!) (self-destruct !" .. name .. "!)) ")
end

elseif ship:isValid() and ship == playership then
class = ship.Class

if cloak_ships[class.Name] then
name = ship.Name
time = mn.getMissionTime()

if timer[callsign .. "_cloaked"] == true and timer[callsign .. "_cloakremaining"] > 0 then
mn.runSEXP(" (ship-stealthy !" .. name .. "!) ")
mn.runSEXP(" (protect-ship !" .. name .. "!) ")
timer[callsign .. "_cloakremaining"] = timer[callsign .. "_cloaktime"] + timer[callsign .. "_cloakremaining"] - time
timer[callsign .. "_cloaktime"] = time
display = "cloaking active: " .. math.floor(timer[callsign .. "_cloakremaining"])

elseif timer[callsign .. "_cloaked"] == true and timer[callsign .. "_cloakremaining"] <= 0 then
mn.runSEXP(" (ship-unstealthy !" .. name .. "!) ")
mn.runSEXP(" (unprotect-ship !" .. name .. "!) ")
timer[callsign .. "_cloakremaining"] = 0
timer[callsign .. "_decloaktime"] = time
timer[callsign .. "_cloaked"] = false
display = "cloaking inactive: " .. math.floor(timer[callsign .. "_cloakremaining"])

else
mn.runSEXP(" (ship-unstealthy !" .. name .. "!) ")
mn.runSEXP(" (unprotect-ship !" .. name .. "!) ")
if timer[callsign .. "_cloakremaining"] < player_cloak_duration then
timer[callsign .. "_cloakremaining"] = time + timer[callsign .. "_cloakremaining"] - timer[callsign .. "_decloaktime"]
timer[callsign .. "_decloaktime"] = time

elseif timer[callsign .. "_cloakremaining"] >= player_cloak_duration then
timer[callsign .. "_cloakremaining"] = player_cloak_duration
end
display = "cloaking inactive: " .. math.floor(timer[callsign .. "_cloakremaining"])
end
else
display = "cloaking not installed"
end
gr.drawString(display, 0.85 * gr.getScreenWidth(), 0)

if spectral_ships[class.Name] then
name = ship.Name
time = mn.getMissionTime()

if timer[callsign .. "_spectral"] == true and timer[callsign .. "_spectralremaining"] > 0 then
mn.runSEXP(" (set-armor-type !" .. name .. "! !True! !SL-Spectral Shield! !Shields!) ")
timer[callsign .. "_spectralremaining"] = timer[callsign .. "_spectraltime"] + timer[callsign .. "_spectralremaining"] - time
timer[callsign .. "_spectraltime"] = time
display = "Spectral shields active: " .. math.floor(timer[callsign .. "_spectralremaining"])

elseif timer[callsign .. "_spectral"] == true and timer[callsign .. "_spectralremaining"] <= 0 then
mn.runSEXP(" (set-armor-type !" .. name .. "! !True! !SL-Standard Shield! !Shields!) ")
timer[callsign .. "_spectralremaining"] = 0
timer[callsign .. "_nospectraltime"] = time
timer[callsign .. "_spectral"] = false
display = "Spectral shields inactive: " .. math.floor(timer[callsign .. "_spectralremaining"])

else
mn.runSEXP(" (set-armor-type !" .. name .. "! !True! !SL-Standard Shield! !Shields!) ")
if timer[callsign .. "_spectralremaining"] < player_spectral_duration then
timer[callsign .. "_spectralremaining"] = time + timer[callsign .. "_spectralremaining"]  - timer[callsign .. "_nospectraltime"]
timer[callsign .. "_nospectraltime"] = time

elseif timer[callsign .. "_spectralremaining"] >= player_spectral_duration then
timer[callsign .. "_spectralremaining"] = player_spectral_duration
end
display = "Spectral shields inactive: " .. math.floor(timer[callsign .. "_spectralremaining"])
end
else
display = "Spectral shields not installed"
end
gr.drawString(display, 0.85 * gr.getScreenWidth(), 0.025 * gr.getScreenHeight())

if ecm_ships[class.Name] then
name = ship.Name
time = mn.getMissionTime()

if timer[callsign .. "_ecm"] == true and timer[callsign .. "_ecmremaining"] > 0 then
mn.runSEXP(" (turret-protect-ship !beam! !" .. name .. "!) ")
mn.runSEXP(" (turret-protect-ship !laser! !" .. name .. "!) ")
mn.runSEXP(" (turret-protect-ship !missile! !" .. name .. "!) ")
mn.runSEXP(" (turret-protect-ship !flak! !" .. name .. "!) ")
timer[callsign .. "_ecmremaining"] = timer[callsign .. "_ecmtime"] + timer[callsign .. "_ecmremaining"] - time
timer[callsign .. "_ecmtime"] = time
display = "ECM active: " .. math.floor(timer[callsign .. "_ecmremaining"])

elseif timer[callsign .. "_ecm"] == true and timer[callsign .. "_ecmremaining"] <= 0 then
mn.runSEXP(" (turret-unprotect-ship !beam! !" .. name .. "!) ")
mn.runSEXP(" (turret-unprotect-ship !laser! !" .. name .. "!) ")
mn.runSEXP(" (turret-unprotect-ship !missile! !" .. name .. "!) ")
mn.runSEXP(" (turret-unprotect-ship !flak! !" .. name .. "!) ")
timer[callsign .. "_ecmremaining"] = 0
timer[callsign .. "_noecmtime"] = time
timer[callsign .. "_ecm"] = false
display = "ECM inactive: " .. math.floor(timer[callsign .. "_ecmremaining"])

else
mn.runSEXP(" (turret-unprotect-ship !beam! !" .. name .. "!) ")
mn.runSEXP(" (turret-unprotect-ship !laser! !" .. name .. "!) ")
mn.runSEXP(" (turret-unprotect-ship !missile! !" .. name .. "!) ")
mn.runSEXP(" (turret-unprotect-ship !flak! !" .. name .. "!) ")
if timer[callsign .. "_ecmremaining"] < player_ecm_duration then
timer[callsign .. "_ecmremaining"] = time + timer[callsign .. "_ecmremaining"]  - timer[callsign .. "_noecmtime"]
timer[callsign .. "_noecmtime"] = time

elseif timer[callsign .. "_ecmremaining"] >= player_ecm_duration then
timer[callsign .. "_ecmremaining"] = player_ecm_duration
end
display = "ECM inactive: " .. math.floor(timer[callsign .. "_ecmremaining"])
end
else
display = "ECM not installed"
end
gr.drawString(display, 0.85 * gr.getScreenWidth(), 0.05 * gr.getScreenHeight())

end
end
]

$State: GS_STATE_GAME_PLAY
$On Key Pressed:
[
local class
local name
local time

if playership:isValid() then
class = playership.Class
name = playership.Name

if hv.Key == "Alt-Shift-G" and cloak_ships[class.Name] then
time = mn.getMissionTime()

if timer[callsign .. "_cloaked"] == false then
timer[callsign .. "_cloaktime"] = time
timer[callsign .. "_cloaked"] = true

else
timer[callsign .. "_decloaktime"] = time
timer[callsign .. "_cloaked"] = false
end
end

if hv.Key == "Alt-Shift-F" and spectral_ships[class.Name] then
time = mn.getMissionTime()

if timer[callsign .. "_spectral"] == false then
timer[callsign .. "_spectraltime"] = time
timer[callsign .. "_spectral"] = true

else
timer[callsign .. "_nospectraltime"] = time
timer[callsign .. "_spectral"] = false
end
end

if hv.Key == "Alt-Shift-Q" and ecm_ships[class.Name] then
time = mn.getMissionTime()

if timer[callsign .. "_ecm"] == false then
timer[callsign .. "_ecmtime"] = time
timer[callsign .. "_ecm"] = true

else
timer[callsign .. "_noecmtime"] = time
timer[callsign .. "_ecm"] = false
end
end

end
]

[\Code]

I know the script is not really optimised because it cycles each ships on frame but I did not find another solution. Anyway it's running with no slowdown.
Title: Re: Handling multiple players
Post by: m!m on January 23, 2017, 04:22:46 am
Lua scripts are a bit weird in multiplayer. AFAIK, every engine instance runs the script but since only the host has complete control of the game state, only those actions will be executed. For SEXPs it's a bit different since they are only run on the host. I'm not sure how that behaves if a SEXP is called from a script.
Title: Re: Handling multiple players
Post by: karajorma on January 25, 2017, 10:29:44 am
SEXPs themselves are only run on the host but they can call a special version of the SEXP to run on the client. I'd be pretty surprised if you could call a SEXP from a script and have it not run into an assertion somewhere that it's running on the host at the very least. More likely it will simply fail.
Title: Re: Handling multiple players
Post by: krevett62 on January 30, 2017, 05:12:49 am
What is strange is that I changed the display to be hud based (with icons for the special effects that change colors when activated) and when running multiplayer the icon shows and change colors as intended on the client (although they are sent to the client via sexps in lua script with hud-set-gauge-active, etc....)

So it seems that hud related sexps are working when run by client, but those that give cloak or protection don't (perhaps to prevent cheating?!)
Title: Re: Handling multiple players
Post by: krevett62 on January 30, 2017, 10:56:25 am
By the way wich object does ship.parent() return ?
Sorry for the stupid question I'm still in a learning stage ^^
Title: Re: Handling multiple players
Post by: karajorma on January 30, 2017, 07:34:31 pm
What is strange is that I changed the display to be hud based (with icons for the special effects that change colors when activated) and when running multiplayer the icon shows and change colors as intended on the client (although they are sent to the client via sexps in lua script with hud-set-gauge-active, etc....)

So it seems that hud related sexps are working when run by client, but those that give cloak or protection don't (perhaps to prevent cheating?!)

More likely is simply that the SEXPs that are working were updated to work in multiplayer (probably by me) while the others were not. Which SEXPs are you using? Also, are you using the script-eval SEXP instead of multi-eval?

EDIT: Also I've noticed that you are calling the SEXPs from the script. I'm not certain that will ever work since they might be called outside of the time when the code is working through the mission SEXPs. That means that the packet won't be built to send them to the client. Since I don't know any lua scripting (code or lua itself) I've never tested that. If the problem is as simple as I'm thinking it might be, the fix is just changing a couple of lines of code. Answer those questions above and then I'll look into it.
Title: Re: Handling multiple players
Post by: krevett62 on January 31, 2017, 01:27:45 am
I'm using the following sexps in the script via the mn.runsexp function:
-ship-stealthy
-ship-unstealthy
-turret-protect
-turret-unprotect
-set-armor-type
-set-primary-ammo
-set-secondary-ammo
-set-num-countermeasures
-repair-subsystem

I'm also using
-hud-set-gauge-active
-hud-set-text

but these two seems to work correctly on client  ;)
Title: Re: Handling multiple players
Post by: karajorma on February 02, 2017, 08:45:08 pm
Yeah, I suspect that's the problem. You need to have a script which runs those SEXPs on the server. It won't do any good to run them on the client because they are all functions which the server keeps track of. If you set the ship stealthy on the server side for instance then as far as the player is concerned that ship is stealthy. But the server doesn't care. And the server is the one who deals with all the AI functions. What you need to do is have the client somehow report to the server what state the ship is in so that the server can execute the SEXPs.
Title: Re: Handling multiple players
Post by: m!m on February 03, 2017, 06:32:12 am
AFAIK, there is no scripting function that runs a specified SEXP on the server.
Title: Re: Handling multiple players
Post by: krevett62 on February 14, 2017, 04:34:29 am
By the way wich object does ship.parent() return ?
Sorry for the stupid question I'm still in a learning stage ^^

Sorry to bump, but doeas anyone has the answer to this question?
Title: Re: Handling multiple players
Post by: m!m on February 14, 2017, 04:46:10 am
As far as I can tell, there is no "parent" function defined on the ship type. There is getParent in subsystem so maybe you mean that?
Title: Re: Handling multiple players
Post by: AdmiralRalwood on February 14, 2017, 04:52:23 am
It returns the object with an index of objp->parent, obviously. :P

"Parent" means something different depending on what you're talking about. The parent of a weapon or beam is the ship that fired it; the parent of a piece of debris or explosion is the ship it came from; the parent of a shockwave is either the ship that the shockwave came from, or the ship that fired the weapon that the shockwave came from.

Ships, however, never have parents; therefore ship.parent(), assuming "ship" is an instance of a ship and not just a confusingly-named variable, will always return an invalid object handle.

EDIT:
As far as I can tell, there is no "parent" function defined on the ship type. There is getParent in subsystem so maybe you mean that?
It's in the "object" class, which "ship" is derived from.
Title: Re: Handling multiple players
Post by: krevett62 on February 14, 2017, 06:04:39 am
Ok, well thanks for the answer Admiral!

In fact I'm searching a way to tell if a ship is handled by AI or by a player.
I'm still trying to make this work in MP by running serverside, but I must have a way to check if a ship is a player ship or an ai ship.
I'm getting stuck with this and the onKeyPressed event I can't tell where it went from (perhaps it just handles locally, that means that there is no way to make it work in MP ^^)
Title: Re: Handling multiple players
Post by: X3N0-Life-Form on February 14, 2017, 06:33:50 am
In fact I'm searching a way to tell if a ship is handled by AI or by a player.
I think that's a recurring problem :).

I found this bit in one of my scripts, although I'm not sure how it would react in a MP context:
      -- Get the player's ship
      local playerShip = mn.Ships[hv.Player.Name]


EDIT - This got me thinking, would it be possible to expose the AI to the scripting API (after 3.8.0 is out ofc) ?
Title: Re: Handling multiple players
Post by: m!m on February 14, 2017, 06:40:09 am
I found this bit in one of my scripts, although I'm not sure how it would react in a MP context:
      -- Get the player's ship
      local playerShip = mn.Ships[hv.Player.Name]
FYI, that code will break if the player is not a ship, e.g. in multiplayer when the player is an observer. Also, hv.Player is already of type ship and you can use it as such. Of course, you still need to check if it's actually a ship with getBreedName().

EDIT - This got me thinking, would it be possible to expose the AI to the scripting API (after 3.8.0 is out ofc) ?
To what extend should it be exposed? Building a fully scripted AI system is pretty much impossible since the rest of the engine assumes that its own AI is used. We may be able to pull something like that of if we make sure that the engine and the AI only talk via a very specific interface which could then be replaced with scripting.
Title: Re: Handling multiple players
Post by: AdmiralRalwood on February 14, 2017, 02:50:03 pm
It would be much simpler (and still needs done) to expose the hard-coded magic numbers throughout the AI code to modders so that mods with significantly different flight models can have functional AI.