openclonk/planet/Objects.ocd/Rules.ocd/Relaunch.ocd/Script.c

486 lines
12 KiB
C

/**
Relaunch Rule
This rule enables and handles relaunches with its various aspects. These are:
* SetInventoryTransfer(bool transfer): inventory of crew is transferred on respawn [default false].
* SetFreeCrew(bool free): whether the crew is free or needs to be bought [default false].
* SetBaseRespawn(bool set): whether to respawn at a nearby base [default false].
* SetLastClonkRespawn(bool b): whether to respawn the last clonk only [default false].
* SetRespawnDelay(int delay): respawn delay in seconds [default 10].
* SetAllowPlayerRestart(bool on): whether a player can select restart in the rule menu.
* SetPerformRestart(bool on): whether this rule actually handles the respawn [default true].
* SetDefaultRelaunchCount(int r): the number of relaunches a player has [default nil == infinte].
* SetInitialRelaunch(bool on): whether a relaunch on round start is done [default true].
The active relaunch rule can be obtained by the global function GetRelaunchRule(). The rule also
keeps track of player's actual number of relaunches which can be modified and accessed by:
* SetPlayerRelaunchCount(int plr, int value): set player relaunch count.
* GetPlayerRelaunchCount(int plr): get player relaunch count.
* DoPlayerRelaunchCount(int plr, int value): add to player relaunch count.
* HasUnlimitedRelaunches(): whether the players have infinite relaunches.
@author Maikel, Sven2, Fulgen
*/
/*-- Settings --*/
// Determines whether the inventory of the crew member is transfered upon respawn.
local inventory_transfer = false;
public func SetInventoryTransfer(bool transfer)
{
inventory_transfer = transfer;
return this;
}
public func GetInventoryTransfer() { return inventory_transfer; }
// Determines whether a crew member needs to be bought.
local free_crew = true;
public func SetFreeCrew(bool free)
{
free_crew = free;
return this;
}
public func GetFreeCrew() { return free_crew; }
// Determines whether the clonk will be respawned at the base.
local respawn_at_base = false;
public func SetBaseRespawn(bool set)
{
respawn_at_base = set;
return this;
}
public func GetBaseRespawn() { return respawn_at_base; }
// Determines whether only the last clonk gets respawned.
local respawn_last_clonk = false;
public func SetLastClonkRespawn(bool b)
{
respawn_last_clonk = b;
return this;
}
public func GetLastClonkRespawn() { return respawn_last_clonk; }
// Determines the amount of time in the relaunch container.
local relaunch_time = 10; // seconds
public func SetRespawnDelay(int delay)
{
relaunch_time = delay;
return this;
}
public func GetRespawnDelay() { return relaunch_time / 36; }
// Determines whether a player can select to restart in a round via the rule menu.
local allow_restart_player = false;
public func SetAllowPlayerRestart(bool on)
{
allow_restart_player = on;
return this;
}
public func GetAllowPlayerRestart() { return allow_restart_player; }
// Determines whether a relaunch is performed by the rule.
local perform_restart = true;
public func SetPerformRestart(bool on)
{
perform_restart = on;
return this;
}
public func GetPerformRestart() { return perform_restart; }
// Determines the default relaunch count.
local default_relaunch_count = nil;
local relaunches = [];
public func SetDefaultRelaunchCount(int r)
{
default_relaunch_count = r;
return this;
}
public func GetDefaultRelaunchCount() { return default_relaunch_count; }
// Determines whether a relaunch is needed on round start.
local initial_relaunch = true;
public func SetInitialRelaunch(bool on)
{
initial_relaunch = on;
return this;
}
public func GetInitialRelaunch() { return initial_relaunch; }
// Determines whether the crew is released after weapon selection.
local hold_crew = false;
public func SetHolding(bool hold)
{
hold_crew = hold;
return this;
}
public func GetHolding() { return hold_crew; }
// Determines whether the player can select the last weapon again.
local disable_last_weapon = false;
local last_used_player_weapons = [];
public func SetLastWeaponUse(bool use)
{
disable_last_weapon = !use;
return this;
}
public func GetLastWeaponUse() { return disable_last_weapon; }
// Not modifiable at the moment.
local respawn_script_players = false;
local clonk_type = Clonk;
/*-- Rule Code --*/
public func Activate(int plr)
{
// Only restart player if enabled unless this is a definition call.
if (this != Rule_Relaunch && !allow_restart_player)
return MessageWindow(this.Description, plr);
// Notify scenario and stop execution if handled by scenario.
if (GameCall("OnPlayerActivatedRestart", plr))
return;
// Remove the player's clonk, including contents.
var clonk = GetCrew(plr);
if (clonk && clonk->GetCrewEnabled())
{
clonk->Kill(clonk, true);
clonk->RemoveObject();
}
return;
}
public func Initialize()
{
ScheduleCall(this, this.CheckDescription, 1, 1);
if (GetScenarioVal("Mode", "Game") == "Melee")
default_relaunch_count = 5;
return;
}
private func CheckDescription()
{
// If neutral flagpoles exist, update name and description.
if(respawn_at_base)
{
if(ObjectCount(Find_ID(Flagpole), Find_Func("IsNeutral")))
{
SetName("$Name2$");
this.Description = "$Description2$";
}
else
{
SetName("$Name3$");
this.Description = "$Description3$";
}
}
else
{
SetName("$Name$");
this.Description = "$Description$";
}
return true;
}
public func InitializePlayer(int plr)
{
_inherited(plr, ...);
relaunches[plr] = default_relaunch_count;
// Check if relaunch is needed.
if (!initial_relaunch || !perform_restart)
return;
// Scenario script callback.
if (GameCall("OnPlayerRelaunch", plr, false))
return;
return DoRelaunch(plr, nil, nil, true);
}
public func OnClonkDeath(object clonk, int killer)
{
if (!clonk || !perform_restart)
return;
var plr = clonk->GetOwner();
if (plr == NO_OWNER || (!respawn_script_players && GetPlayerType(plr) == C4PT_Script)) return;
if (default_relaunch_count != nil)
{
relaunches[plr]--;
if (relaunches[plr] < 0)
{
EliminatePlayer(plr);
return;
}
}
GameCall("OnPlayerRelaunch", plr, true);
return DoRelaunch(plr, clonk, nil);
}
private func RespawnAtBase(int plr, object clonk)
{
var base = GetRelaunchBase(plr, clonk);
if (base)
return [base->GetX(), base->GetY() + base->GetBottom()];
return;
}
private func TransferInventory(object from, object to)
{
if (!from || !to) return;
// Drop some items that cannot be transferred (such as connected pipes and dynamite igniters)
var i = from->ContentsCount(), contents;
while (i--)
{
if (contents = from->Contents(i))
{
if (contents->~IsDroppedOnDeath(from))
{
contents->Exit();
}
else
{
// The new clonk doesn't burn. To be consistent, also extinguish contents
contents->Extinguish();
}
}
}
return to->GrabContents(from);
}
private func GetRelaunchBase(int plr, object clonk)
{
plr = plr ?? clonk->GetOwner();
// Neutral flagpoles are preferred respawn points, because they are used as the only respawn points in missions.
var base = FindObject(Find_ID(Flagpole), Find_Func("IsNeutral"), Sort_Random());
if (clonk)
clonk->FindObject(Find_ID(Flagpole), Find_Func("IsNeutral"), clonk->Sort_Distance());
// If there are no neutral flagpoles, find closest base owned by the player (or team) and try to buy a clonk.
if (!base)
{
base = FindObject(Find_Func("IsBaseBuilding"), Find_Allied(plr), Sort_Random());
if (clonk)
base = clonk->FindObject(Find_Func("IsBaseBuilding"), Find_Allied(plr), clonk->Sort_Distance());
}
return base;
}
public func DoRelaunch(int plr, object clonk, array position, bool no_creation)
{
if (!GetPlayerName(plr))
return;
if (respawn_last_clonk && GetCrewCount(plr) >= 1)
return;
if (respawn_at_base)
position = RespawnAtBase(plr, clonk);
position = position ?? GameCall("RelaunchPosition", plr, GetPlayerTeam(plr));
position = position ?? this->FindRelaunchPos(plr);
var spawn;
// Position array either has the form [x, y] or [[x, y], [x, y], ...].
if (GetType(position) == C4V_Array)
{
if (GetType(position[0]) == C4V_Array)
spawn = position[Random(GetLength(position))];
else
spawn = position;
}
// If no spawn has been found set it to the middle of the landscape, this should not happen.
spawn = spawn ?? [LandscapeWidth() / 2, LandscapeHeight() / 2];
var new_clonk;
if (!no_creation)
{
if (free_crew)
{
new_clonk = CreateObjectAbove(clonk_type, spawn[0], spawn[1], plr);
if (!new_clonk)
return;
new_clonk->MakeCrewMember(plr);
}
else
{
var base = GetRelaunchBase(plr, clonk);
if (!base)
return;
// Try to buy a crew member at the base.
var pay_plr = base->GetOwner();
// Payment in neutral bases by clonk owner.
if (pay_plr == NO_OWNER)
pay_plr = plr;
new_clonk = base->~DoBuy(clonk_type, plr, pay_plr, clonk);
if (new_clonk)
new_clonk->Exit();
}
}
else
{
new_clonk = GetCrew(plr);
if (!new_clonk)
return;
}
if (inventory_transfer)
TransferInventory(clonk, new_clonk);
new_clonk->SetPosition(spawn[0], spawn[1] - new_clonk->GetBottom(), plr);
if (!GetCursor(plr) || GetCursor(plr) == clonk)
SetCursor(plr, new_clonk);
new_clonk->DoEnergy(new_clonk.Energy ?? 100000);
if (relaunch_time)
{
var container = new_clonk->CreateObject(RelaunchContainer, 0, new_clonk->GetBottom(), plr);
container->SetRelaunchTime(relaunch_time, hold_crew);
container->StartRelaunch(new_clonk);
}
return true;
}
protected func FindRelaunchPos(int plr)
{
var loc = FindLocation(Loc_Or(Loc_Sky(), Loc_Tunnel()), Loc_Space(20, CNAT_Top), Loc_Wall(CNAT_Bottom));
if (loc == nil)
return nil;
return [loc.x, loc.y];
}
/*-- Scenario Saving --*/
public func SaveScenarioObject(props, ...)
{
if (!inherited(props, ...))
return false;
// Custom properties
props->Remove("Name"); // updated by initialization
props->Remove("Description"); // updated by initialization
if (inventory_transfer)
props->AddCall("InventoryTransfer", this, "SetInventoryTransfer", inventory_transfer);
if (free_crew)
props->AddCall("FreeCrew", this, "SetFreeCrew", free_crew);
if (respawn_at_base)
props->AddCall("BaseRespawn", this, "SetBaseRespawn", respawn_at_base);
return true;
}
/*-- Globals --*/
// Returns the active relaunch rule, creates one if no exists.
global func GetRelaunchRule()
{
return FindObject(Find_ID(Rule_Relaunch)) || CreateObject(Rule_Relaunch);
}
global func IsActiveRelaunchRule()
{
return !!FindObject(Find_ID(Rule_Relaunch));
}
/*-- Player Relaunches --*/
public func SetPlayerRelaunchCount(int plr, int value)
{
if (HasUnlimitedRelaunches())
return;
relaunches[plr] = value;
Scoreboard->SetPlayerData(plr, "relaunches", relaunches[plr]);
return;
}
public func GetPlayerRelaunchCount(int plr)
{
return relaunches[plr];
}
public func DoPlayerRelaunchCount(int plr, int value)
{
if(HasUnlimitedRelaunches())
return;
relaunches[plr] += value;
Scoreboard->SetPlayerData(plr, "relaunches", relaunches[plr]);
return;
}
public func HasUnlimitedRelaunches()
{
return default_relaunch_count == nil;
}
/*-- Editor --*/
public func Definition(proplist def)
{
if (!def.EditorProps) def.EditorProps = {};
def.EditorProps.inventory_transfer = { Name="$InventoryTransfer$", EditorHelp="$InventoryTransferHelp$", Type="bool", Set="SetInventoryTransfer" };
def.EditorProps.free_crew = { Name="$FreeCrew$", EditorHelp="$FreeCrewHelp$", Type="bool", Set="SetFreeCrew" };
def.EditorProps.respawn_at_base = {
Name = "$RespawnAtBase$",
EditorHelp = "$RespawnAtBaseHelp$",
Type = "bool",
Set = "SetBaseRespawn"
};
def.EditorProps.hold = {
Name = "$Holding$",
EditorHelp = "$HoldingHelp$",
Type = "bool",
Set = "Setholding"
};
def.EditorProps.respawn_delay = {
Name = "$RespawnDelay$",
EditorHelp = "$RespawnDelayHelp$",
Type = "int",
Set = "SetRespawnDelay"
};
def.EditorProps.relaunch_count = {
Name = "$RelaunchCount$",
EditorHelp = "$RelaunchCountHelp$",
Type = "int",
Set = "SetDefaultRelaunchCount"
};
}
/*-- Proplist --*/
local Name = "$Name$";
local Description = "$Description$";
local Visibility = VIS_Editor;
local EditorPlacementLimit = 1; // Rules are to be placed only once