Author Topic: Handling multiple players  (Read 1947 times)

0 Members and 1 Guest are viewing this topic.

Handling multiple players
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.
« Last Edit: January 04, 2017, 07:37:27 am by krevett62 »

 

Offline m!m

  • 211
Re: Handling multiple players
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.

 

Offline karajorma

  • King Louie - Jungle VIP
  • Administrator
  • 214
    • Karajorma's Freespace FAQ
Re: Handling multiple players
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.
Karajorma's Freespace FAQ. It's almost like asking me yourself.

[ Diaspora ] - [ Seeds Of Rebellion ] - [ Mind Games ]

 
Re: Handling multiple players
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?!)

 
Re: Handling multiple players
By the way wich object does ship.parent() return ?
Sorry for the stupid question I'm still in a learning stage ^^

 

Offline karajorma

  • King Louie - Jungle VIP
  • Administrator
  • 214
    • Karajorma's Freespace FAQ
Re: Handling multiple players
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.
« Last Edit: January 30, 2017, 07:42:36 pm by karajorma »
Karajorma's Freespace FAQ. It's almost like asking me yourself.

[ Diaspora ] - [ Seeds Of Rebellion ] - [ Mind Games ]

 
Re: Handling multiple players
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  ;)

 

Offline karajorma

  • King Louie - Jungle VIP
  • Administrator
  • 214
    • Karajorma's Freespace FAQ
Re: Handling multiple players
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.
Karajorma's Freespace FAQ. It's almost like asking me yourself.

[ Diaspora ] - [ Seeds Of Rebellion ] - [ Mind Games ]

 

Offline m!m

  • 211
Re: Handling multiple players
AFAIK, there is no scripting function that runs a specified SEXP on the server.

 
Re: Handling multiple players
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?

 

Offline m!m

  • 211
Re: Handling multiple players
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?

 

Offline AdmiralRalwood

  • 211
  • The Cthulhu programmer himself!
    • Skype
    • Steam
    • Twitter
Re: Handling multiple players
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.
Ph'nglui mglw'nafh Codethulhu GitHub wgah'nagl fhtagn.

schrödinbug (noun) - a bug that manifests itself in running software after a programmer notices that the code should never have worked in the first place.

When you gaze long into BMPMAN, BMPMAN also gazes into you.

"I am one of the best FREDders on Earth" -General Battuta

<Aesaar> literary criticism is vladimir putin

<MageKing17> "There's probably a reason the code is the way it is" is a very dangerous line of thought. :P
<MageKing17> Because the "reason" often turns out to be "nobody noticed it was wrong".
(the very next day)
<MageKing17> this ****ing code did it to me again
<MageKing17> "That doesn't really make sense to me, but I'll assume it was being done for a reason."
<MageKing17> **** ME
<MageKing17> THE REASON IS PEOPLE ARE STUPID
<MageKing17> ESPECIALLY ME

<MageKing17> God damn, I do not understand how this is breaking.
<MageKing17> Everything points to "this should work fine", and yet it's clearly not working.
<MjnMixael> 2 hours later... "God damn, how did this ever work at all?!"
(...)
<MageKing17> so
<MageKing17> more than two hours
<MageKing17> but once again we have reached the inevitable conclusion
<MageKing17> How did this code ever work in the first place!?

<@The_E> Welcome to OpenGL, where standards compliance is optional, and error reporting inconsistent

<MageKing17> It was all working perfectly until I actually tried it on an actual mission.

<IronWorks> I am useful for FSO stuff again. This is a red-letter day!
* z64555 erases "Thursday" and rewrites it in red ink

<MageKing17> TIL the entire homing code is held up by shoestrings and duct tape, basically.

 
Re: Handling multiple players
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 ^^)

 
Re: Handling multiple players
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) ?

 

Offline m!m

  • 211
Re: Handling multiple players
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.

 

Offline AdmiralRalwood

  • 211
  • The Cthulhu programmer himself!
    • Skype
    • Steam
    • Twitter
Re: Handling multiple players
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.
Ph'nglui mglw'nafh Codethulhu GitHub wgah'nagl fhtagn.

schrödinbug (noun) - a bug that manifests itself in running software after a programmer notices that the code should never have worked in the first place.

When you gaze long into BMPMAN, BMPMAN also gazes into you.

"I am one of the best FREDders on Earth" -General Battuta

<Aesaar> literary criticism is vladimir putin

<MageKing17> "There's probably a reason the code is the way it is" is a very dangerous line of thought. :P
<MageKing17> Because the "reason" often turns out to be "nobody noticed it was wrong".
(the very next day)
<MageKing17> this ****ing code did it to me again
<MageKing17> "That doesn't really make sense to me, but I'll assume it was being done for a reason."
<MageKing17> **** ME
<MageKing17> THE REASON IS PEOPLE ARE STUPID
<MageKing17> ESPECIALLY ME

<MageKing17> God damn, I do not understand how this is breaking.
<MageKing17> Everything points to "this should work fine", and yet it's clearly not working.
<MjnMixael> 2 hours later... "God damn, how did this ever work at all?!"
(...)
<MageKing17> so
<MageKing17> more than two hours
<MageKing17> but once again we have reached the inevitable conclusion
<MageKing17> How did this code ever work in the first place!?

<@The_E> Welcome to OpenGL, where standards compliance is optional, and error reporting inconsistent

<MageKing17> It was all working perfectly until I actually tried it on an actual mission.

<IronWorks> I am useful for FSO stuff again. This is a red-letter day!
* z64555 erases "Thursday" and rewrites it in red ink

<MageKing17> TIL the entire homing code is held up by shoestrings and duct tape, basically.