A basic implementation of vanilla based spawn selection.
NOTE: THIS APPLIES TO ALL SERVERS. WHETHER THEY ARE USING OFFICIAL SERVER PLUGIN, SQMOD OR ANYTHING ELSE AS FAR AS I KNOW
Fixed in later server updates.
— Why?
People on our unofficial VCMP Discord figured out (specifically @aXXo) that by just staying at the skin/spawn selection screen for a few minutes, will make VCMP server start leaking memory, which will start lagging and eventually just crash the server.
— How to solve it?
The solution is to not let players stay at skin selection for too long. Some servers perform various things at Request class and Request spawn events, therefore its not a solution for them to just force spawn player all the time, one alternative is to make your own spawn selection.
You can place this code inside a spawnselector.nut file and just load it from sqmod.ini as Compile
Code: [Select]
— Usage:
Code: [Select]
— Methods:
These are the custom signals that you can use with this
Code: [Select]
Forgive me if the code is ugly :P I just did it for a friend of mine cuz he was busy.
Fixed in later server updates.
— Why?
People on our unofficial VCMP Discord figured out (specifically @aXXo) that by just staying at the skin/spawn selection screen for a few minutes, will make VCMP server start leaking memory, which will start lagging and eventually just crash the server.
— How to solve it?
The solution is to not let players stay at skin selection for too long. Some servers perform various things at Request class and Request spawn events, therefore its not a solution for them to just force spawn player all the time, one alternative is to make your own spawn selection.
You can place this code inside a spawnselector.nut file and just load it from sqmod.ini as Compile
__SPAWN_SELECTOR_STATICS__ <- {
SPAWN_SELECTION_ID = -1,
CLASS_ID = 0,
PLAYER_LAST_USED_CLASS = {}
};
class SpawnSelector {
static PlayerRequestClass = SqCreateSignal("PlayerRequestClass");
static PlayerRequestSpawn = SqCreateSignal("PlayerRequestSpawn");
static PlayerSpawn = SqCreateSignal("PlayerSpawn");
static SPAWN_SELECTION_KEYS = {
ARROW_LEFT = SqKeybind.Create(true, 0x25, 0, 0),
ARROW_RIGHT = SqKeybind.Create(true, 0x27, 0, 0),
SELECTION_CTRL = SqKeybind.Create(true, 0x11, 0, 0),
SELECTION_NUM0 = SqKeybind.Create(true, 0x60, 0, 0),
SELECTION_LMB = SqKeybind.Create(true, 0x01, 0, 0)
};
ID = null;
Camera = null;
Classes = null;
Players = null;
constructor(cameraData = null) {
::__SPAWN_SELECTOR_STATICS__.SPAWN_SELECTION_ID++;
ID = ::__SPAWN_SELECTOR_STATICS__.SPAWN_SELECTION_ID;
Camera = {
position = ::Vector3(694.918, -652.151, 12.1429),
lookAt = ::Vector3(687.879, -652.088, 12.08),
playerPosition = ::Vector3(687.879, -652.088, 12.08)
}
Classes = [];
Players = {};
if(cameraData)
SetCameraData(cameraData.position, cameraData.lookAt, cameraData.playerPosition);
}
/*** Camera ***/
function SetCameraData(position, lookAt, playerPosition) {
SetCameraPosition(position);
SetCameraLookAt(lookAt);
SetCameraPlayerPosition(playerPosition);
}
function SetCameraPosition(position) {
Camera.position = position;
}
function SetCameraLookAt(lookAt) {
Camera.lookAt = lookAt;
}
function SetCameraPlayerPosition(position) {
Camera.playerPosition = position;
}
/***
Methods
***/
/*** Player ***/
function AddPlayer(player, forceSetClass = 0) {
if(Classes.len() == 0)
throw("Class selector has no classes");
if(!IsActiveForPlayer(player)) {
Players[player.ID] <- SpawnSelectorPlayer(player, Classes[forceSetClass]);
player.SetOption(::getconsttable().SqPlayerOption.Controllable, false);
player.World = player.UniqueWorld;
player.Pos = Camera.playerPosition;
player.CameraPosition(Camera.position.x, Camera.position.y, Camera.position.z, Camera.lookAt.x, Camera.lookAt.y, Camera.lookAt.z);
}
}
function RemovePlayer(player) {
if(IsActiveForPlayer(player)) {
Players.rawget(player.ID).destroy();
Players.rawdelete(player.ID);
player.SetOption(::getconsttable().SqPlayerOption.Controllable, true);
player.World = 1;
}
}
function IsActiveForPlayer(player) {
return Players.rawin(player.ID);
}
function RequestSpawn(player) {
if(IsActiveForPlayer(player)) {
if(!PlayerRequestSpawn.Consume(player, Players.rawget(player.ID)))
return false;
SpawnPlayer(player);
}
}
function SpawnPlayer(player) {
local classID = Players.rawget(player.ID).ClassID;
Classes[classID].Apply(player);
RemovePlayer(player);
PlayerSpawn.Consume(player);
::__SPAWN_SELECTOR_STATICS__.PLAYER_LAST_USED_CLASS.rawset(player.ID, classID);
}
/*** Class ***/
function AddClass(team, color, skin, pos, angle, wep1, ammo1, wep2, ammo2, wep3, ammo3) {
Classes.push(::SpawnSelectorClass(team, color, skin, pos, angle, wep1, ammo1, wep2, ammo2, wep3, ammo3));
}
function DisplayNextClass(player) {
if(Players.rawin(player.ID)) {
local playerSelectorInstance = Players.rawget(player.ID);
local nextClassID = playerSelectorInstance.ClassID + 1;
if(nextClassID >= Classes.len())
nextClassID = 0;
playerSelectorInstance.DisplayClass(Classes[nextClassID]);
}
}
function DisplayPreviousClass(player) {
if(Players.rawin(player.ID)) {
local playerSelectorInstance = Players.rawget(player.ID);
local previousClassID = playerSelectorInstance.ClassID - 1;
if(previousClassID < 0)
previousClassID = Classes.len()-1;
playerSelectorInstance.DisplayClass(Classes[previousClassID]);
}
}
}
class SpawnSelectorClass {
ID = null;
Team = null;
Color = null;
Skin = null;
Pos = null;
Angle = null;
Wep1 = null;
Wep2 = null;
Wep3 = null;
Ammo1 = null;
Ammo2 = null;
Ammo3 = null;
constructor(team, color, skin, pos, angle, wep1, ammo1, wep2, ammo2, wep3, ammo3) {
ID = ::__SPAWN_SELECTOR_STATICS__.CLASS_ID;
Team = team;
Color = color;
Skin = skin;
Pos = pos;
Angle = angle;
Wep1 = wep1;
Wep2 = wep2;
Wep3 = wep3;
Ammo1 = ammo1;
Ammo2 = ammo2;
Ammo3 = ammo3;
::__SPAWN_SELECTOR_STATICS__.CLASS_ID++;
}
function Apply(player) {
player.Skin = Skin;
player.Team = Team;
player.Color = Color;
player.StripWeapons();
player.SetWeapon(Wep1, Ammo1);
player.SetWeapon(Wep2, Ammo2);
player.SetWeapon(Wep3, Ammo3);
player.Pos = Pos;
player.Angle = Angle;
player.RestoreCamera();
}
}
class SpawnSelectorPlayer {
Player = null;
ClassID = null;
constructor(playerInstance, classInstance = null) {
Player = playerInstance;
if(classInstance)
DisplayClass(classInstance);
}
function destroy() {
Player = null;
ClassID = null;
}
function DisplayClass(classInstance) {
ClassID = classInstance.ID;
Player.Skin = classInstance.Skin;
Player.Team = classInstance.Team;
Player.Color = classInstance.Color;
Player.StripWeapons();
Player.SetWeapon(classInstance.Wep1, classInstance.Ammo1); // We just need to display the first weapon only for now
::SpawnSelector.PlayerRequestClass.Consume(Player, classInstance);
}
}
— Usage:
SqCore.On().ScriptLoaded.Connect(this, function() {
CustomSpawnSelection <- SpawnSelector();
CustomSpawnSelection.AddClass(1, Color3( 100, 126, 255 ), 62, ::Vector3(0, 0, 10), 1, 20, 999, 9, 250, 18, 500);
CustomSpawnSelection.AddClass(2, Color3( 255, 255, 0 ), 61, ::Vector3(0, 0, 10), 1, 20, 999, 9, 250, 18, 500);
});
SqCore.On().PlayerRequestClass.Connect(this, function(player, ...) {
player.Spawn();
});
SqCore.On().PlayerSpawn.Connect(this, function(player) {
local lastClassID = (__SPAWN_SELECTOR_STATICS__.PLAYER_LAST_USED_CLASS.rawin(player.ID) ? __SPAWN_SELECTOR_STATICS__.PLAYER_LAST_USED_CLASS.rawget(player.ID) : 0);
CustomSpawnSelection.AddPlayer(player, lastClassID);
});
SqCore.On().PlayerKeyPress.Connect(this, function(player, key) {
if(CustomSpawnSelection.IsActiveForPlayer(player)) {
switch(key) {
case SpawnSelector.SPAWN_SELECTION_KEYS.SELECTION_CTRL:
case SpawnSelector.SPAWN_SELECTION_KEYS.SELECTION_LMB:
case SpawnSelector.SPAWN_SELECTION_KEYS.SELECTION_NUM0:
CustomSpawnSelection.RequestSpawn(player);
break;
case SpawnSelector.SPAWN_SELECTION_KEYS.ARROW_LEFT:
CustomSpawnSelection.DisplayNextClass(player);
break;
case SpawnSelector.SPAWN_SELECTION_KEYS.ARROW_RIGHT:
CustomSpawnSelection.DisplayPreviousClass(player);
break;
}
}
});
function SpawnSelector_DiscardPlayer(...) {
if(CustomSpawnSelection.IsActiveForPlayer(vargv[0])) {
CustomSpawnSelection.RemovePlayer(vargv[0]);
}
if(vargv.len() == 3) { // PlayerDestroyed
__SPAWN_SELECTOR_STATICS__.PLAYER_LAST_USED_CLASS.rawdelete(vargv[0].ID);
}
}
SqCore.On().PlayerKilled.Connect(SpawnSelector_DiscardPlayer);
SqCore.On().PlayerWasted.Connect(SpawnSelector_DiscardPlayer);
SqCore.On().PlayerDestroyed.Connect(SpawnSelector_DiscardPlayer);
— Methods:
- SpawnSelector::Constructor(optional: Camera Data); — Camera Data is a table: {position = Vector3(...), lookAt = Vector3(...), playerPosition = Vector3(...)}
- SpawnSelector::SetCameraPosition(Vector3 position)
- SpawnSelector::SetCameraLookAt(Vector3 lookAt)
- SpawnSelector::SetCameraPlayerPosition(Vector3 playerPosition)
- SpawnSelector::AddClass(...); — Follows the same parameters as the SqMod default
These are the custom signals that you can use with this
/***
Signals
***/
SqSignal("PlayerRequestClass").Connect(function(player, classInstance) {
printf("PlayerRequestClass: %d", classInstance.ID);
});
SqSignal("PlayerRequestSpawn").Connect(function(player, classInstance) {
/***
return true — Stop the handler execution here, Don't execute any later connected handlers to this signal
return false — Returns false to the .Consume(...) method, treat it similar to return 0 in official plugin
***/
print("PlayerRequestSpawn");
return true;
});
SqSignal("PlayerSpawn").Connect(function(player) {
print("PlayerSpawn");
});
Forgive me if the code is ugly :P I just did it for a friend of mine cuz he was busy.