openclonk/planet/Objects.ocd/HUD.ocd/Scoreboard.ocd/Script.c

328 lines
8.8 KiB
C

/**
Scoreboard
Provides an additional abstraction layer on top of the scoreboard-functions
Attention: Do not include this. Use the function as specified with f.e. Scoreboard->Init(...)
*/
// do not manipulate externally
static Scoreboard_keys; // [{key = ?, title = ?, ..}, ..]
static Scoreboard_data; // [{key = value, ..}, ..]
static Scoreboard_unique_ids;
static Scoreboard_title_set;
static const Scoreboard_Y_title = SBRD_Caption;
static const Scoreboard_X_title = SBRD_Caption;
// wrapper for Scoreboard->NewEntry, adds a new entry for a player with the tagged player name as the title
public func NewPlayerEntry(int plr)
{
return Scoreboard->NewEntry(GetPlayerID(plr), GetTaggedPlayerName(plr));
}
public func RemovePlayerEntry(int plr)
{
return Scoreboard->RemoveEntry(GetPlayerID(plr));
}
// adds a new entry (row) to the scoreboard, will return the ID of the added entry
// the parameter new_id might be nil in which case a unique new ID is chosen
public func NewEntry(
int new_id /* unique ID for the new entry, can be nil */
, string new_title /* text for the first column */
)
{
// not initialized yet? Then init empty.
if (!Scoreboard_keys) Init([]);
// check for duplicates
if(new_id)
{
var index = -1;
for(var i = 0; i < GetLength(Scoreboard_data); ++i)
{
if(Scoreboard_data[i].ID != new_id) continue;
index = i;
break;
}
// duplicate?
if(index != -1) return nil;
}
else // no ID provided, get new one
{
new_id = ++Scoreboard_unique_ids;
}
PushBack(Scoreboard_data, {ID = new_id, title = new_title});
Scoreboard->Update(new_id);
return new_id;
}
// removes an entry (row) from the scoreboard
public func RemoveEntry(
int remove_id /* unique ID for the new entry, can be nil */
)
{
var i, len = GetLength(Scoreboard_data);
for(i = 0; i < len; ++i) if(Scoreboard_data[i].ID == remove_id) break;
if (i == len) return false; // not found
RemoveArrayIndex(Scoreboard_data, i);
// Clear all columns
for (var col in Scoreboard_keys) SetScoreboardData(remove_id, col.index);
SetScoreboardData(remove_id, Scoreboard_X_title);
return true;
}
// sets a value for a specific key of a scoreboard entry for a player and updates it
// that entry must have been created earlier with ScoreboarNewPlayerEntry or manually with Scoreboard->NewEntry
public func SetPlayerData(
int plr /* player number for the player entry to manipulate */
, string key /* name of the key assign value to */
, to /* value to assign, might be ID, string, int or bool */
, int sort_parameter /* parameter used for sorting. if nil, 'to' is used if possible*/
)
{
var ID = GetPlayerID(plr);
return Scoreboard->SetData(ID, key, to, sort_parameter);
}
// sets a value for a specific key of any entry in the scoreboard and updates it
// the entry must have been created with Scoreboard->NewEntry earlier
public func SetData(
int ID /* ID of the entry to manipulate */
, string key /* name of the key to assign value to */
, to /* new value of the key, might be int, string bool or id */
, int sort_parameter /* parameter that is used for sorting of the entry. if nil, 'to' is used if possible*/
)
{
var index = -1;
for(var i = 0; i < GetLength(Scoreboard_data); ++i)
{
if(Scoreboard_data[i].ID == ID)
{
index = i;
break;
}
}
if(index == -1) return;
Scoreboard_data[index][key] = to;
if(sort_parameter)
Scoreboard_data[index][Format("%s_", key)] = sort_parameter;
Scoreboard->Update(ID);
}
// updates the display of the scoreboard
// there is usually no need to call it manually since Scoreboard->SetData calls Update
public func Update(
int ID /* ID of the entry to update */
, int index /* optional, internal index. leave at nil.*/
)
{
if(index == nil)
{
for(var i = 0; i < GetLength(Scoreboard_data); ++i)
{
if(Scoreboard_data[i].ID != ID) continue;
index = i;
break;
}
if(index == nil) return;
}
var data = Scoreboard_data[index];
// title - playername f.e.
SetScoreboardData(data.ID, Scoreboard_X_title, data.title, -1);
var len = GetLength(Scoreboard_keys);
for(var i = 0; i < len; ++i)
{
var col = Scoreboard_keys[i];
if(!col.key) continue;
if(col.key == "title") continue; // already set
// aquire value
var value = col.default;
if(data[col.key] != nil) value = data[col.key];
// get sorting parameter
var sort = data[Format("%s_", col.key)];
if(sort == nil)
if(GetType(value) == C4V_Int) sort = value;
// the column provides a conditional function
if(col.conditional)
{
value = col->conditional(value);
// overwrite old sort?
if(GetType(value) == C4V_Int) sort = value;
}
if(value == nil) value = "";
else if(GetType(value) == C4V_Int) {value = Format("%d", value);}
else if(GetType(value) == C4V_Def) value = Format("{{%i}}", value);
else if(GetType(value) == C4V_Bool) {sort = value; if(value) value = "X"; else value = "";}
SetScoreboardData(data.ID, col.index, value, sort);
}
UpdateSort();
return true;
}
public func UpdateSort()
{
// do sorting for fields neccessary
var len = GetLength(Scoreboard_keys);
for(var i = len-1; i >= 0; --i)
{
var col = Scoreboard_keys[i];
if(!col.key) continue;
if(!col.sorted) continue;
SortScoreboard(col.index, col.desc);
}
return true;
}
// updates the whole scoreboard
public func UpdateAll()
{
// for every row, do update
var len = GetLength(Scoreboard_data);
for(var i = 0; i < len; ++i)
{
Scoreboard->Update(Scoreboard_data[i].ID, i);
}
return true;
}
// remove a column from the scoreboard
public func RemoveColumn(string key)
{
var col, data;
if (!key) FatalError("Scoreboard::RemoveColumn: Key required!");
// find key
for (col in Scoreboard_keys) if (col.key == key) break;
if (!col || col.key != key) return false; // key not found. fail but not fatal.
// for every row, remove data for key
for (data in Scoreboard_data)
{
data[key] = data[Format("%s_", key)] = nil;
SetScoreboardData(data.ID, col.index);
}
// resort
SortScoreboard(col.index, col.desc);
return true;
}
// initializes the scoreboard with certain columns and attributes
// can be called multiple times and scales
// values must be an array of proplists where each entry stands for one column
// the entries can have the following attributes:
// "key" is required and used later for Scoreboard->SetData
// {key (string), priority (int), sorted (bool), desc (bool), title (string), default (any)}
// one row with key = "title" is necessary and will be automatically added if left out and not existent
public func Init(array values /* see description */)
{
// first call?
if(Scoreboard_unique_ids == nil)
{
Scoreboard_unique_ids = 0xffffff;
Scoreboard_keys = [];
Scoreboard_data = [];
if(!Scoreboard_title_set)
Scoreboard->SetTitle("Scoreboard");
}
// merge arrays
for(var val in values)
{
if(val == nil) continue;
var index = nil;
for(var i = GetLength(Scoreboard_keys)-1; i >= 0; --i)
{
if(Scoreboard_keys[i].key != val.key) continue;
index = i;
break;
}
if(index == nil)
PushBack(Scoreboard_keys, val); // new entry
else Scoreboard_keys[index] = val; // overwrite
}
// title property set?
var found_title = false;
for(var col in Scoreboard_keys)
{
if(col.key != "title") continue;
found_title = true;
break;
}
// if not, add
if(!found_title)
PushBack(Scoreboard_keys, {key = "title", title = ""});
// sort, selection sort
var len = GetLength(Scoreboard_keys);
for(var x = 0; x < len - 1; ++x)
{
var max = -1, max_val = nil;
for(var i = x; i < len; ++i)
{
var data = Scoreboard_keys[i];
if(max_val == nil || (data.priority > max_val))
{
max = i;
max_val = data.priority;
}
}
if(max == -1) break;
var t = Scoreboard_keys[x];
Scoreboard_keys[x] = Scoreboard_keys[max];
Scoreboard_keys[max] = t;
}
// assign indices to scoreboard data, they are now sorted - also create scoreboard
var len = GetLength(Scoreboard_keys);
for(var i = 0; i < len; ++i)
{
Scoreboard_keys[i].index = i;
if(Scoreboard_keys[i].key == "title") // title has special index and no headline
{
Scoreboard_keys[i].index = Scoreboard_X_title;
// don't set headline for title (first) column, because that would change the scoreboard title
continue;
}
// check title
if(GetType(Scoreboard_keys[i].title) == C4V_Def)
Scoreboard_keys[i].title = Format("{{%i}}", Scoreboard_keys[i].title);
var data = -0xffffff;
if(Scoreboard_keys[i].desc) data = 0xffffff;
SetScoreboardData(Scoreboard_Y_title, Scoreboard_keys[i].index, Scoreboard_keys[i].title, data);
}
// setup scoreboard
Scoreboard->UpdateAll();
// show scoreboard, only initially - after that it's the player's choice
DoScoreboardShow(1);
}
// sets the title of the scoreboard, standard is "Scoreboard"
public func SetTitle(string title)
{
SetScoreboardData(SBRD_Caption, SBRD_Caption, title);
Scoreboard_title_set = true;
}