forked from Mirrors/openclonk
453 lines
14 KiB
C
453 lines
14 KiB
C
/**
|
|
Player start
|
|
Controls start position and modalities
|
|
|
|
@author Sven2
|
|
*/
|
|
|
|
|
|
/* Definition */
|
|
|
|
local starting_players = { Option="all" };
|
|
local starting_knowledge = { Option="all" };
|
|
local starting_crew; // const arrays not supported yet
|
|
local starting_material;
|
|
local starting_wealth = 0;
|
|
local starting_base_material;
|
|
local respawn_material;
|
|
local clonk_max_contents_count, clonk_max_energy; // Override properties for clonks
|
|
local view_lock = false;
|
|
local zoom_min, zoom_max, zoom_set;
|
|
local Name = "$Name$";
|
|
local Description = "$Description$";
|
|
local Visibility = VIS_Editor;
|
|
local Plane = 311;
|
|
local players_started; // Array of players for which this was the start point
|
|
|
|
public func Definition(def)
|
|
{
|
|
def.starting_crew = GetDefaultCrew();
|
|
def.starting_material = GetDefaultMaterial();
|
|
def.starting_base_material = GetDefaultBaseMaterial();
|
|
if (!def.EditorProps) def.EditorProps = {};
|
|
def.EditorProps.starting_players = EditorBase.PlayerMask;
|
|
def.EditorProps.starting_knowledge = { Name="$Knowledge$", Type="enum", OptionKey="Option", Options = [
|
|
{ Name="$None$" },
|
|
{ Name="$All$", Value={ Option="all" } },
|
|
{ Name="$AllExcept$", Value={ Option="allexcept", Data=[] }, ValueKey="Data", Delegate=EditorBase.IDSet },
|
|
{ Name="$Specific$", Value={ Option="idlist", Data=[] }, ValueKey="Data", Delegate=EditorBase.IDSet },
|
|
] };
|
|
def.EditorProps.starting_crew = EditorBase->GetConditionalIDList("IsClonk", "$Crew$", Clonk);
|
|
def.EditorProps.starting_material = new EditorBase.ItemPlusParameterList { Name="$StartingMaterial$", EditorHelp="$StartingMaterialHelp$" };
|
|
def.EditorProps.starting_wealth = { Name="$Wealth$", Type="int", Min=0 };
|
|
def.EditorProps.starting_base_material = new EditorBase.IDList { Name="$BaseMaterial$", EditorHelp="$BaseMaterialHelp$" };
|
|
def.EditorProps.clonk_max_contents_count = { Name="$ClonkMaxContentsCount$", EditorHelp="$ClonkMaxContentsCountHelp$", Type="enum", Options = [
|
|
{ Name=Format("$Default$ (%d)", Clonk.MaxContentsCount) }, { Name="$Custom$", Value=Clonk.MaxContentsCount, Delegate={ Type="int", Min=0, Max=10 } } ] };
|
|
def.EditorProps.clonk_max_energy = { Name="$ClonkMaxEnergy$", EditorHelp="$ClonkMaxEnergyHelp$", Type="enum", Options = [
|
|
{ Name=Format("$Default$ (%d)", Clonk.MaxEnergy/1000) }, { Name="$Custom$", Value=Clonk.MaxEnergy/1000, Delegate={ Type="int", Min=1, Max=100000 } } ] };
|
|
def.EditorProps.respawn_material = { Name="$RespawnMaterial$", Type="enum", Set="SetRespawnMaterial", Save="RespawnMaterial", Options = [
|
|
{ Name="$None$" },
|
|
{ Name="$SameAsStartingMaterial$", Value="starting_material" },
|
|
{ Name="$Custom$", Value=[], Type=C4V_Array, Delegate=new EditorBase.ItemPlusParameterList { Name="$RespawnMaterial$", EditorHelp="$RespawnMaterialHelp$" } },
|
|
] };
|
|
def.EditorProps.view_lock = { Name="$ViewLock$", Priority = -100, Type="bool" };
|
|
def.EditorProps.zoom_min = { Name="$ZoomMin$", Set="SetZoomMin", Priority = -101, Type="enum", OptionKey="Option", Options = [
|
|
{ Name="$Default$" },
|
|
{ Name="$Custom$", Value=150, Delegate={ Type="int", Min=50, Max=750, Step=50 } }
|
|
] };
|
|
def.EditorProps.zoom_max = { Name="$ZoomMax$", Set="SetZoomMax", Priority = -102, Type="enum", OptionKey="Option", Options = [
|
|
{ Name="$Default$" },
|
|
{ Name="$Custom$", Value=750, Delegate={ Type="int", Min=150, Max=100000, Step=50 } }
|
|
] };
|
|
def.EditorProps.zoom_set = { Name="$ZoomSet$", Set="SetZoomSet", Priority = -103, Type="enum", OptionKey="Option", Options = [
|
|
{ Name="$Default$" },
|
|
{ Name="$Custom$", Value=300, Delegate={ Type="int", Min=150, Max=750, Step=50 } }
|
|
] };
|
|
return true;
|
|
}
|
|
|
|
public func GetDefaultCrew() { return [{id=Clonk, count=1}]; }
|
|
public func GetDefaultMaterial() { return [Shovel, Hammer, Axe]; }
|
|
public func GetDefaultBaseMaterial() { return [{id=Clonk, count=999999}]; }
|
|
|
|
public func Initialize()
|
|
{
|
|
// Re-init default
|
|
starting_crew = GetDefaultCrew();
|
|
starting_material = GetDefaultMaterial();
|
|
starting_base_material = GetDefaultBaseMaterial();
|
|
return true;
|
|
}
|
|
|
|
|
|
/* Interface */
|
|
|
|
public func SetStartingPlayers(string setting, param)
|
|
{
|
|
if (setting)
|
|
starting_players = { Option=setting, Data=param };
|
|
else
|
|
starting_players = nil; // None
|
|
return true;
|
|
}
|
|
|
|
public func SetStartingKnowledge(string setting, param)
|
|
{
|
|
if (setting)
|
|
starting_knowledge = { Option=setting, Data=param };
|
|
else
|
|
starting_knowledge = nil; // None
|
|
return true;
|
|
}
|
|
|
|
public func SetStartingCrew(array new_crew)
|
|
{
|
|
starting_crew = new_crew;
|
|
return true;
|
|
}
|
|
|
|
public func SetStartingMaterial(array new_material)
|
|
{
|
|
// ID+count conversion (old style)
|
|
if (new_material && GetLength(new_material) && new_material[0].id && new_material[0].count && !new_material[0]->~GetName())
|
|
{
|
|
starting_material = [];
|
|
var n = 0;
|
|
for (var idlist_entry in new_material)
|
|
for (var i = 0; i < idlist_entry.count; ++i)
|
|
starting_material[n++] = idlist_entry.id;
|
|
}
|
|
else
|
|
{
|
|
starting_material = new_material;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public func SetStartingWealth(int new_wealth)
|
|
{
|
|
starting_wealth = new_wealth;
|
|
return true;
|
|
}
|
|
|
|
public func SetStartingBaseMaterial(array new_material)
|
|
{
|
|
starting_base_material = new_material;
|
|
return true;
|
|
}
|
|
|
|
public func SetClonkMaxContentsCount(int new_clonk_max_contents_count)
|
|
{
|
|
clonk_max_contents_count = new_clonk_max_contents_count;
|
|
return true;
|
|
}
|
|
|
|
public func SetClonkMaxEnergy(int new_clonk_max_energy)
|
|
{
|
|
clonk_max_energy = new_clonk_max_energy;
|
|
return true;
|
|
}
|
|
|
|
public func SetRespawnMaterial(new_material)
|
|
{
|
|
respawn_material = new_material;
|
|
}
|
|
|
|
public func SetViewLock(bool lock)
|
|
{
|
|
view_lock = lock;
|
|
}
|
|
|
|
public func SetZoomMin(int zoom)
|
|
{
|
|
zoom_min = zoom;
|
|
this.EditorProps.zoom_max.Options[1].Delegate.Min = zoom_min;
|
|
this.EditorProps.zoom_set.Options[1].Delegate.Min = zoom_min;
|
|
SetZoomSet(Max(zoom_set ?? this.EditorProps.zoom_set.Options[1].Value, zoom_min));
|
|
}
|
|
|
|
public func SetZoomMax(int zoom)
|
|
{
|
|
zoom_max = zoom;
|
|
this.EditorProps.zoom_max.Options[1].Delegate.Max = zoom_max;
|
|
this.EditorProps.zoom_set.Options[1].Delegate.Max = zoom_max;
|
|
SetZoomSet(Min(zoom_set ?? this.EditorProps.zoom_set.Options[1].Value, zoom_max));
|
|
}
|
|
|
|
public func SetZoomSet(int zoom)
|
|
{
|
|
zoom_set = zoom;
|
|
for (var plr in GetPlayers(C4PT_User))
|
|
InitializeView(plr);
|
|
}
|
|
|
|
|
|
/* Player initialization checks */
|
|
|
|
public func InitializePlayer(int plr, x, y, base, team, script_id)
|
|
{
|
|
// Find which one to evaluate
|
|
var possible_startpoints = FindObjects(Find_ID(PlayerStart), Find_Func("IsStartFor", plr));
|
|
var n = GetLength(possible_startpoints);
|
|
if (!n) return false;
|
|
// This callback will be done for every start point (unfortunately)
|
|
// So ensure initialization happens only once
|
|
// (Could speed up things by setting a variable in the other start points to avoid the redundant search. Meh it's just initialization anyway.)
|
|
// Note that this method assumes that starting points are returned in a predictable order
|
|
if (this != possible_startpoints[0]) return false;
|
|
// Pick best starting point: Away from other players, especially enemies
|
|
for (var startpoint in possible_startpoints)
|
|
{
|
|
var other_clonks = startpoint->FindObjects(Find_Distance(50), Find_OCF(OCF_CrewMember));
|
|
var hostile = 0;
|
|
for (var c in other_clonks) if (Hostile(c->GetOwner(), plr)) ++hostile;
|
|
startpoint.penalty = GetLength(other_clonks) + hostile*1000;
|
|
}
|
|
SortArrayByProperty(possible_startpoints, "penalty");
|
|
var n_best = 1, best_penalty = possible_startpoints[0].penalty;
|
|
if (n>1) while (possible_startpoints[n_best].penalty == best_penalty) if (++n_best == n) break;
|
|
// Launch there
|
|
possible_startpoints[Random(n_best)]->DoPlayerStart(plr);
|
|
return true;
|
|
}
|
|
|
|
public func IsStartFor(int plr)
|
|
{
|
|
return EditorBase->EvaluatePlayerMask(starting_players , plr);
|
|
}
|
|
|
|
|
|
/* Actual player initialization */
|
|
|
|
local is_handling_player_spawn; // temp var set to nonzero during initial player spawn (to differentiate from respawn)
|
|
|
|
public func DoPlayerStart(int plr)
|
|
{
|
|
// Player launch controlled by this object!
|
|
if (!players_started) players_started = [];
|
|
players_started[GetLength(players_started)] = plr;
|
|
++is_handling_player_spawn;
|
|
// Give wealth
|
|
SetWealth(plr, starting_wealth);
|
|
// Set base material
|
|
InitializeBaseMaterial(plr);
|
|
// Create requested crew
|
|
InitializeCrew(plr);
|
|
// Put contents into crew
|
|
InitializeMaterial(plr);
|
|
// Give knowledge
|
|
InitializeKnowledge(plr);
|
|
// Handle viewport settings
|
|
InitializeView(plr);
|
|
--is_handling_player_spawn;
|
|
return true;
|
|
}
|
|
|
|
public func RemovePlayer(int plr)
|
|
{
|
|
// Remove number from players_started list
|
|
if (players_started)
|
|
{
|
|
var idx = GetIndexOf(players_started, plr);
|
|
if (idx >= 0)
|
|
{
|
|
var n = GetLength(players_started) - 1;
|
|
players_started[idx] = players_started[n];
|
|
SetLength(players_started, n);
|
|
}
|
|
}
|
|
}
|
|
|
|
public func OnClonkRecruitment(clonk, plr)
|
|
{
|
|
// New clonk recruitment: Apply default clonk settings
|
|
if (players_started && GetIndexOf(players_started, plr) >= 0)
|
|
{
|
|
ApplyCrewSettings(clonk);
|
|
if (!is_handling_player_spawn && respawn_material)
|
|
{
|
|
if (respawn_material == "starting_material")
|
|
{
|
|
// Same as startign material
|
|
InitializeMaterial(plr);
|
|
}
|
|
else
|
|
{
|
|
// Array of custom respawn material
|
|
for (var idlist_entry in respawn_material)
|
|
clonk->CreateContents(idlist_entry.id, idlist_entry.count);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func ApplyCrewSettings(object crew)
|
|
{
|
|
if (GetType(clonk_max_contents_count)) crew->~SetMaxContentsCount(clonk_max_contents_count);
|
|
if (GetType(clonk_max_energy))
|
|
{
|
|
crew->~SetMaxEnergy(clonk_max_energy*1000);
|
|
crew->DoEnergy(clonk_max_energy);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private func InitializeCrew(int plr)
|
|
{
|
|
// Collect IDs of crew to create
|
|
var requested_crew = [], n=0;
|
|
for (var idlist_entry in starting_crew)
|
|
for (var i = 0; i < idlist_entry.count; ++i)
|
|
requested_crew[n++] = idlist_entry.id;
|
|
// Match them to existing crew
|
|
for (var i = GetCrewCount(plr)-1; i>=0; --i)
|
|
{
|
|
var obj = GetCrew(plr, i);
|
|
if (obj) {
|
|
var idx = GetIndexOf(requested_crew, obj->GetID());
|
|
if (idx >= 0)
|
|
{
|
|
obj->SetPosition(GetX(), GetY() + GetDefHeight()/2 - obj->GetDefHeight()/2);
|
|
requested_crew[idx] = nil;
|
|
}
|
|
else
|
|
obj->RemoveObject(); // not in list: Kill
|
|
}
|
|
}
|
|
// Create any missing crew
|
|
for (var def in requested_crew)
|
|
if (def)
|
|
{
|
|
var obj = CreateObjectAbove(def, 0, GetDefHeight()/2, plr);
|
|
if (obj)
|
|
obj->MakeCrewMember(plr);
|
|
}
|
|
// Apply crew settings
|
|
for (var i = GetCrewCount(plr)-1; i>=0; --i)
|
|
{
|
|
var obj = GetCrew(plr, i);
|
|
if (obj)
|
|
ApplyCrewSettings(obj);
|
|
}
|
|
// Done!
|
|
return true;
|
|
}
|
|
|
|
private func InitializeBaseMaterial(int plr)
|
|
{
|
|
// Set base material to minimum of current material and material given by this object
|
|
if (starting_base_material)
|
|
{
|
|
for (var entry in starting_base_material)
|
|
{
|
|
var current_num = GetBaseMaterial(plr, entry.id);
|
|
if (current_num < entry.count)
|
|
{
|
|
SetBaseMaterial(plr, entry.id, entry.count);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private func InitializeMaterial(int plr)
|
|
{
|
|
// Spread material across clonks. Try to fill them evenly and avoid giving the same item twice to the same clonk
|
|
// So e.g. each clonk can get one shovel
|
|
for (var idlist_entry in starting_material)
|
|
{
|
|
var best_target = nil, target_score;
|
|
var obj = EditorBase->CreateItemPlusParameter(idlist_entry, GetX(), GetY() + GetDefHeight() / 2, plr);
|
|
if (!obj || !obj.Collectible) continue;
|
|
var id = idlist_entry.id;
|
|
for (var j=0; j < GetCrewCount(plr); ++j)
|
|
{
|
|
var clonk = GetCrew(plr, j);
|
|
if (clonk)
|
|
{
|
|
var clonk_score = 0;
|
|
// High penalty: Already has item of same type
|
|
clonk_score += clonk->ContentsCount(id)*1000;
|
|
// Low penalty: Already has items
|
|
clonk_score += clonk->ContentsCount();
|
|
if (!best_target || clonk_score < target_score)
|
|
{
|
|
best_target = clonk;
|
|
target_score = clonk_score;
|
|
}
|
|
}
|
|
}
|
|
if (best_target) best_target->Collect(obj); // May fail due to contents full
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private func InitializeKnowledge(int plr)
|
|
{
|
|
var def;
|
|
if (!starting_knowledge) return true; // No knowledge
|
|
if (starting_knowledge.Option == "all" || starting_knowledge.Option == "allexcept")
|
|
{
|
|
var i=0, exceptlist = [];
|
|
if (starting_knowledge.Option == "allexcept") exceptlist = starting_knowledge.Data;
|
|
while (def = GetDefinition(i++))
|
|
if (!(def->GetCategory() & (C4D_Rule | C4D_Goal | C4D_Environment)))
|
|
if (GetIndexOf(exceptlist, def) == -1)
|
|
SetPlrKnowledge(plr, def);
|
|
}
|
|
else if (starting_knowledge.Option == "idlist")
|
|
{
|
|
for (def in starting_knowledge.Data)
|
|
SetPlrKnowledge(plr, def);
|
|
}
|
|
else
|
|
{
|
|
// Unknown option
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private func InitializeView(int plr)
|
|
{
|
|
SetPlayerViewLock(plr, view_lock);
|
|
// Zoom limit "nil" means default limits.
|
|
SetPlayerZoomByViewRange(plr, zoom_min, zoom_min, PLRZOOM_Direct | PLRZOOM_LimitMin);
|
|
SetPlayerZoomByViewRange(plr, zoom_max, zoom_max, PLRZOOM_Direct | PLRZOOM_LimitMax);
|
|
// If no zoom value is specified: Assume what the player has set currently is the default.
|
|
if (zoom_set != nil)
|
|
{
|
|
SetPlayerZoomByViewRange(plr, zoom_set, zoom_set, PLRZOOM_Direct | PLRZOOM_Set);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/* Scenario saving */
|
|
|
|
public func SaveScenarioObject(props, ...)
|
|
{
|
|
if (!inherited(props, ...)) return false;
|
|
if (!DeepEqual(starting_players, GetID().starting_players))
|
|
if (starting_players)
|
|
props->AddCall("Players", this, "SetStartingPlayers", Format("%v", starting_players.Option), starting_players.Data);
|
|
else
|
|
props->AddCall("Players", this, "SetStartingPlayers", nil);
|
|
if (!DeepEqual(starting_knowledge, GetID().starting_knowledge))
|
|
if (starting_knowledge)
|
|
props->AddCall("Knowledge", this, "SetStartingKnowledge", Format("%v", starting_knowledge.Option), starting_knowledge.Data);
|
|
else
|
|
props->AddCall("Knowledge", this, "SetStartingKnowledge", nil);
|
|
if (!DeepEqual(starting_crew, GetID().starting_crew)) props->AddCall("Crew", this, "SetStartingCrew", starting_crew);
|
|
if (!DeepEqual(starting_material, GetID().starting_material)) props->AddCall("Material", this, "SetStartingMaterial", starting_material);
|
|
if (!DeepEqual(starting_base_material, GetID().starting_base_material)) props->AddCall("Material", this, "SetStartingBaseMaterial", starting_base_material);
|
|
if (starting_wealth != GetID().starting_wealth) props->AddCall("Wealth", this, "SetStartingWealth", starting_wealth);
|
|
if (GetType(clonk_max_contents_count)) props->AddCall("ClonkMaxContentsCount", this, "SetClonkMaxContentsCount", clonk_max_contents_count);
|
|
if (GetType(clonk_max_energy)) props->AddCall("ClonkMaxEnergy", this, "SetClonkMaxEnergy", clonk_max_energy);
|
|
if (view_lock != nil) props->AddCall("ViewLock", this, "SetViewLock", view_lock);
|
|
if (zoom_min != nil) props->AddCall("ZoomMin", this, "SetZoomMin", zoom_min);
|
|
if (zoom_max != nil) props->AddCall("ZoomMax", this, "SetZoomMax", zoom_max);
|
|
if (zoom_set != nil) props->AddCall("ZoomSet", this, "SetZoomSet", zoom_set);
|
|
return true;
|
|
}
|