forked from Mirrors/openclonk
380 lines
9.9 KiB
C
380 lines
9.9 KiB
C
/*--
|
|
Checkpoint
|
|
Author: Maikel
|
|
|
|
The parkour goal uses checkpoints to allow for user defined routes.
|
|
A checkpoint can have different modes, indicated with a bitmask:
|
|
*None - Not a Checkpoint.
|
|
*Start - Start of the parkour.
|
|
*Finish - End of the parkour.
|
|
*Respawn - The clonk can respawn at this CP.
|
|
*Check - This checkpoint must be cleared in order to complete the parkour.
|
|
*Ordered - These checkpoints must be cleared in the right order.
|
|
*Team - All players of a team must have cleared this CP.
|
|
*Bonus - Player receives a bonus if he cleares this CP.
|
|
--*/
|
|
|
|
|
|
/*-- Checkpoint modes --*/
|
|
local cp_mode;
|
|
static const PARKOUR_CP_None = 0;
|
|
static const PARKOUR_CP_Start = 1;
|
|
static const PARKOUR_CP_Finish = 2;
|
|
static const PARKOUR_CP_Respawn = 4;
|
|
static const PARKOUR_CP_Check = 8;
|
|
static const PARKOUR_CP_Ordered = 16;
|
|
static const PARKOUR_CP_Team = 32;
|
|
static const PARKOUR_CP_Bonus = 64;
|
|
|
|
// particle definition used for the effect around the check point
|
|
local checkpoint_particles;
|
|
|
|
public func SetCPMode(int mode)
|
|
{
|
|
// PARKOUR_CP_Start always occurs alone.
|
|
if (mode & PARKOUR_CP_Start)
|
|
mode = PARKOUR_CP_Start;
|
|
// PARKOUR_CP_Finish only in combination with PARKOUR_CP_Team.
|
|
if (mode & PARKOUR_CP_Finish)
|
|
mode = mode & (PARKOUR_CP_Finish | PARKOUR_CP_Team) ;
|
|
// PARKOUR_CP_Ordered must have PARKOUR_CP_Check and a number.
|
|
if (mode & PARKOUR_CP_Ordered)
|
|
{
|
|
mode = mode | PARKOUR_CP_Check;
|
|
// Set CP number.
|
|
SetCPNumber(ObjectCount(Find_ID(GetID()), Find_Func("GetCPNumber")) + 1);
|
|
}
|
|
cp_mode = mode;
|
|
DoGraphics();
|
|
return;
|
|
}
|
|
|
|
public func GetCPMode() { return cp_mode; }
|
|
|
|
public func FindCPMode(int mode) { return cp_mode & mode; }
|
|
|
|
/*-- Checkpoint controller --*/
|
|
local cp_con;
|
|
|
|
public func SetCPController(object con)
|
|
{
|
|
cp_con = con;
|
|
return;
|
|
}
|
|
|
|
/*-- Checkpoint number --*/
|
|
local cp_num;
|
|
|
|
public func SetCPNumber(int num)
|
|
{
|
|
cp_num = num;
|
|
return;
|
|
}
|
|
|
|
public func GetCPNumber() { return cp_num; }
|
|
|
|
/*-- Checkpoint size --*/
|
|
local cp_size;
|
|
|
|
public func SetCPSize(int size)
|
|
{
|
|
cp_size = BoundBy(size, 10, 100);
|
|
return;
|
|
}
|
|
|
|
public func GetCPSize() { return cp_size; }
|
|
|
|
/*-- Initialize --*/
|
|
|
|
local cleared_by_plr; // Array to keep track of players which were already here.
|
|
|
|
protected func Initialize()
|
|
{
|
|
checkpoint_particles =
|
|
{
|
|
Size = PV_KeyFrames(0, 0, 0, 250, 10, 500, 0, 750, 10),
|
|
Alpha = PV_KeyFrames(0, 0, 255, 500, 0, 501, 255, 1000, 0),
|
|
BlitMode = GFX_BLIT_Additive,
|
|
Attach = ATTACH_Front
|
|
};
|
|
cleared_by_plr = [];
|
|
cp_mode = PARKOUR_CP_Check;
|
|
cp_size = 20;
|
|
UpdateGraphics();
|
|
AddEffect("IntCheckpoint", this, 100, 1, this);
|
|
return;
|
|
}
|
|
|
|
/*-- Checkpoint status --*/
|
|
|
|
// Returns whether this checkpoint has been cleared by player.
|
|
public func ClearedByPlayer(int plr)
|
|
{
|
|
var plrid = GetPlayerID(plr);
|
|
return cleared_by_plr[plrid];
|
|
}
|
|
|
|
// Returns whether this checkpoint has been cleared by team.
|
|
public func ClearedByTeam(int team)
|
|
{
|
|
if (!team)
|
|
return false;
|
|
if (cp_mode & PARKOUR_CP_Team)
|
|
{
|
|
// PARKOUR_CP_Team: Cleared if all players of the team have cleared the checkpoint.
|
|
for (var i = 0; i < GetPlayerCount(); i++)
|
|
if (GetPlayerTeam(GetPlayerByIndex(i)) == team)
|
|
if (!ClearedByPlayer(GetPlayerByIndex(i)))
|
|
return false;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Not PARKOUR_CP_Team: Cleared if one player has cleared the checkpoint.
|
|
for (var i = 0; i < GetPlayerCount(); i++)
|
|
if (GetPlayerTeam(GetPlayerByIndex(i)) == team)
|
|
if (ClearedByPlayer(GetPlayerByIndex(i)))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Whether this checkpoint is active for a player.
|
|
public func IsActiveForPlayer(int plr)
|
|
{
|
|
// PARKOUR_CP_Finish: Check all PARKOUR_CP_Check checkpoints.
|
|
if (cp_mode & PARKOUR_CP_Finish)
|
|
{
|
|
for (var cp in FindObjects(Find_ID(GetID())))
|
|
if (cp->GetCPMode() & PARKOUR_CP_Check)
|
|
if (!cp->ClearedByPlayer(plr))
|
|
return false;
|
|
return true;
|
|
}
|
|
// PARKOUR_CP_Ordered: Check previous PARKOUR_CP_Ordered checkpoint.
|
|
if (cp_mode & PARKOUR_CP_Ordered)
|
|
{
|
|
// First ordered checkpoint is always active.
|
|
if (GetCPNumber() == 1)
|
|
return true;
|
|
for (var cp in FindObjects(Find_ID(GetID()), Find_Func("GetCPNumber")))
|
|
if (cp->GetCPNumber() + 1 == GetCPNumber())
|
|
{
|
|
var team = GetPlayerTeam(plr);
|
|
if (cp->GetCPMode() & PARKOUR_CP_Team && team)
|
|
{
|
|
if (cp->ClearedByTeam(team))
|
|
return true;
|
|
}
|
|
else if (cp->ClearedByPlayer(plr))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
// Other modes are always active.
|
|
return true;
|
|
}
|
|
|
|
// Whether this checkpoint is active for a team.
|
|
public func IsActiveForTeam(int team)
|
|
{
|
|
if (!team)
|
|
return false;
|
|
// Checkpoint is active for a team if it is active for one of its members.
|
|
for (var i = 0; i < GetPlayerCount(); i++)
|
|
if (GetPlayerTeam(GetPlayerByIndex(i)) == team)
|
|
if (IsActiveForPlayer(GetPlayerByIndex(i)))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/*-- Checkpoint activity --*/
|
|
|
|
protected func FxIntCheckpointTimer(object target, effect, int fxtime)
|
|
{
|
|
// Check every 5 frames.
|
|
if (!(fxtime % 5))
|
|
CheckForClonks();
|
|
UpdateGraphics(fxtime);
|
|
return FX_OK;
|
|
}
|
|
|
|
protected func CheckForClonks()
|
|
{
|
|
// Only check if controlled by a parkour goal.
|
|
if (!cp_con)
|
|
return;
|
|
// Loop through all clonks inside the checkpoint.
|
|
for (var clonk in FindObjects(Find_OCF(OCF_CrewMember), Find_Distance(cp_size)))
|
|
{
|
|
var plr = clonk->GetOwner();
|
|
var team = GetPlayerTeam(plr);
|
|
var plrid = GetPlayerID(plr);
|
|
// Check whether this CP is already activated for player or its team.
|
|
if (!IsActiveForPlayer(plr) && !IsActiveForTeam(team))
|
|
continue;
|
|
// Check respawn status.
|
|
if (cp_mode & PARKOUR_CP_Respawn)
|
|
cp_con->SetPlayerRespawnCP(plr, this); // Notify parkour goal.
|
|
// If already done by player -> continue.
|
|
if (ClearedByPlayer(plr))
|
|
continue;
|
|
// Check checkpoint status.
|
|
if (cp_mode & PARKOUR_CP_Check)
|
|
{
|
|
var team_clear = !ClearedByTeam(team);
|
|
cleared_by_plr[plrid] = true;
|
|
Sound("UI::Cleared", false, 100, plr);
|
|
cp_con->AddPlayerClearedCP(plr, this); // Notify parkour goal.
|
|
if (ClearedByTeam(team) && team_clear)
|
|
cp_con->AddTeamClearedCP(team, this); // Notify parkour goal.
|
|
}
|
|
// Check finish status.
|
|
if (cp_mode & PARKOUR_CP_Finish)
|
|
{
|
|
Sound("UI::Cleared", false, 100, plr);
|
|
cleared_by_plr[plrid] = true;
|
|
if (team)
|
|
{
|
|
if (ClearedByTeam(team))
|
|
cp_con->PlayerReachedFinishCP(plr, this); // Notify parkour goal.
|
|
else
|
|
cp_con->AddPlayerClearedCP(plr, this); // Notify parkour goal.
|
|
}
|
|
else
|
|
{
|
|
cp_con->PlayerReachedFinishCP(plr, this); // Notify parkour goal.
|
|
}
|
|
}
|
|
// Check bonus.
|
|
if (cp_mode & PARKOUR_CP_Bonus)
|
|
GameCall("GivePlrBonus", plr, this);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*-- Checkpoint appearance --*/
|
|
|
|
// Mode graphics.
|
|
protected func DoGraphics()
|
|
{
|
|
// Clear all overlays first.
|
|
for (var i = 1; i <= 3; i++)
|
|
SetGraphics(nil, nil, i);
|
|
// Start & Finish.
|
|
if (cp_mode & PARKOUR_CP_Start || cp_mode & PARKOUR_CP_Finish)
|
|
{
|
|
SetGraphics("", ParkourFlag, 1, GFXOV_MODE_Base);
|
|
SetObjDrawTransform(350, 0, 2000, 0, 350, 2000, 1);
|
|
SetClrModulation(RGBa(255, 255, 255, 160) , 1);
|
|
}
|
|
// Ordered, display numbers up to 99.
|
|
if (cp_mode & PARKOUR_CP_Ordered)
|
|
{
|
|
var shift = 0;
|
|
if (GetCPNumber() >= 10)
|
|
{
|
|
SetGraphics(Format("%d", GetCPNumber()/10), Icon_Number, 3, GFXOV_MODE_Base);
|
|
SetObjDrawTransform(300, 0, -4500, 0, 300, 0, 3);
|
|
SetClrModulation(RGBa(255, 255, 255, 128) , 3);
|
|
shift = 1;
|
|
}
|
|
SetGraphics(Format("%d", GetCPNumber()%10), Icon_Number, 2, GFXOV_MODE_Base);
|
|
SetObjDrawTransform(300, 0, shift * 4500, 0, 300, 0, 2);
|
|
SetClrModulation(RGBa(255, 255, 255, 128) , 2);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Player graphics.
|
|
protected func UpdateGraphics(int time)
|
|
{
|
|
// Create two sparks at opposite sides.
|
|
var angle = (time * 10) % 360;
|
|
var color = GetColorByAngle(angle);
|
|
checkpoint_particles.R = (color >> 16) & 0xff;
|
|
checkpoint_particles.G = (color >> 8) & 0xff;
|
|
checkpoint_particles.B = (color >> 0) & 0xff;
|
|
CreateParticle("SphereSpark", Sin(angle, cp_size), -Cos(angle, cp_size), 0, 0, 18 * 5, checkpoint_particles);
|
|
return;
|
|
}
|
|
|
|
protected func GetColorByAngle(int angle)
|
|
{
|
|
// Get cleared count.
|
|
var cnt = 0;
|
|
for (var i = 0; i < GetPlayerCount(); i++)
|
|
if (ClearedByPlayer(GetPlayerByIndex(i)) || (cp_mode & PARKOUR_CP_Start))
|
|
cnt++;
|
|
if (!cnt)
|
|
return RGBa(255, 255, 255, 192);
|
|
|
|
var prt = 360 / cnt;
|
|
var j = 0;
|
|
// Find the right player.
|
|
for (var i = 0; i < GetPlayerCount(); i++)
|
|
{
|
|
var plr = GetPlayerByIndex(i);
|
|
if (ClearedByPlayer(plr) || (cp_mode & PARKOUR_CP_Start))
|
|
{
|
|
if (angle >= j * prt && angle < (j + 1) * prt)
|
|
return GetPlayerColor(plr);
|
|
j++;
|
|
}
|
|
}
|
|
|
|
// Should not happen...
|
|
return RGBa(255, 255, 255, 192);
|
|
}
|
|
|
|
/*-- Misc --*/
|
|
|
|
// Clears all materials behind a checkpoint.
|
|
public func ClearCPBack()
|
|
{
|
|
var x = GetX();
|
|
var y = GetY();
|
|
// Approximate by rectangles for now.
|
|
for (var d = 0; d <= cp_size; d++)
|
|
{
|
|
var dx = x - d;
|
|
var dy = y - Sqrt(cp_size**2 - d**2);
|
|
var wdt = 2 * d;
|
|
var hgt = 2 * Sqrt(cp_size**2 - d**2);
|
|
ClearFreeRect(dx, dy, wdt, hgt);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Storing checkpoints in Objects.c
|
|
public func SaveScenarioObject(props)
|
|
{
|
|
if (!inherited(props, ...)) return false;
|
|
var v = GetCPSize();
|
|
if (v != 20) props->AddCall("Checkpoint", this, "SetCPSize", v);
|
|
// Checkpoints without a goal? Use regular saving.
|
|
if (!cp_con)
|
|
{
|
|
|
|
if (v = GetCPMode()) props->AddCall("Checkpoint", this, "SetCPMode", GetBitmaskNameByValue(v, "PARKOUR_CP_"));
|
|
if (v = GetCPNumber()) props->AddCall("Checkpoint", this, "SetCPNumber", v);
|
|
return true;
|
|
}
|
|
// Special checkpoints
|
|
props->RemoveCreation();
|
|
if (cp_mode & PARKOUR_CP_Start)
|
|
props->AddCall(SAVEOBJ_Creation, cp_con, "SetStartpoint", GetX(), GetY());
|
|
else if (cp_mode & PARKOUR_CP_Finish)
|
|
props->AddCall(SAVEOBJ_Creation, cp_con, "SetFinishpoint", GetX(), GetY(), !!(cp_mode & PARKOUR_CP_Team));
|
|
else
|
|
{
|
|
var other_cp_modes = cp_mode & (~PARKOUR_CP_Finish) & (~PARKOUR_CP_Start);
|
|
props->AddCall(SAVEOBJ_Creation, cp_con, "AddCheckpoint", GetX(), GetY(), GetBitmaskNameByValue(other_cp_modes, "PARKOUR_CP_"));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/*-- Proplist --*/
|
|
local Name = "$Name$";
|