openclonk/planet/Defense.ocf/Defense.ocd/Homebase.ocd/Script.c

390 lines
9.2 KiB
C

/**
Homebase
Manage buyable stuff and technology upgrades in Gidl
@authors Sven2
*/
static g_homebases;
local buy_menu;
local base_material; // array of base material entries
local last_buy_idx;
local techs, requirement_names;
local is_selling; // temp to prevent recursion from object removal
// Technology fields - queried by objects using them
local tech_load_speed_multiplier = 100;
local tech_shooting_strength_multiplier = 0;
local tech_life = 1;
static g_quickbuy_items;
// Types for purchasable stuff
local ITEMTYPE_Weapon = {
free_rebuy = true,
stack_count = -1
};
local ITEMTYPE_Consumable = {
auto_rebuy = true
};
local ITEMTYPE_Technology = {
remove_after_buy = true,
callback = "GainTechnology",
extra_width = 2,
};
/* Creation / Destruction */
public func Construction(...)
{
// Manage pointers
if (GetOwner() < 0) FatalError("Invalid Homebase owner");
if (!g_homebases) g_homebases = [];
if (g_homebases[GetOwner()]) g_homebases[GetOwner()]->RemoveObject(); // remove old (shouldn't be here)
g_homebases[GetOwner()] = this;
// Init
base_material = [];
techs = {};
requirement_names = {};
last_buy_idx = -1;
// Buy menu
buy_menu = CreateObject(GUI_BuyMenu, 0,0, GetOwner());
buy_menu->SetHomebase(this);
// Get available items
GameCall("FillHomebase", this);
// Buy menu always open (but hidden at start)
buy_menu->Open();
return true;
}
public func Destruction()
{
if (buy_menu) buy_menu->RemoveObject();
return true;
}
public func SetQuickbuyItems(array list)
{
g_quickbuy_items = list;
}
public func AddCaption(string title, array requirements)
{
return AddHomebaseItem({is_caption = true, title=title, requirements=requirements});
}
public func AddHomebaseItem(proplist entry)
{
// Default name and description
if (entry.item)
{
if (!entry.name) entry.name = entry.item.Name;
if (!entry.desc) entry.desc = entry.item.Description;
}
// Remember tech name mapping
if (entry.tech) requirement_names[entry.tech] = entry.name;
// Add to end of list
var idx = GetLength(base_material);
base_material[idx] = entry;
var quickbuy_idx = GetIndexOf(g_quickbuy_items, entry.item);
if (quickbuy_idx >= 0) entry.hotkey = GetPlayerControlAssignment(GetOwner(), CON_QuickBuy0+quickbuy_idx, true);
UpdateIndexedItem(idx);
return entry;
}
public func UpdateIndexedItem(int index)
{
if (index >= 0 && buy_menu)
{
var entry = base_material[index];
if (entry.hidden) return true;
var available = true;
if (entry.requirements)
for (var req in entry.requirements)
if (!techs[req])
available = false;
var tier;
if (entry.tiers)
{
tier = techs[entry.tech];
entry.graphic = Format(entry.graphics, tier+1);
entry.cost = entry.costs[tier];
}
if (entry.is_caption)
return buy_menu->UpdateCaption(entry.title, available, entry, index);
else
return buy_menu->UpdateBuyEntry(entry.item, available, entry, index, index == last_buy_idx, tier);
}
return false;
}
public func GetEntryByID(def id)
{
for (var i = 0; i < GetLength(base_material); i++)
if (base_material[i].item == id)
return i;
return nil;
}
public func GetEntryInformation(int entry_idx)
{
// Fill with current information for this entry
var entry = base_material[entry_idx];
// Append (Tier x/y) to name
if (entry.tiers)
{
if (!entry.base_name) entry.base_name = entry.name;
var tier = techs[entry.tech];
entry.name = Format("%s ($Tier$ %d/%d)", entry.base_name, tier+1, entry.tiers);
}
// Compose info message
// Info message: Requirements
var msg = "";
if (entry.requirements)
{
var req_str = "";
var req_sep = "";
for (var req in entry.requirements)
{
var clr = 0xff00;
if (!techs[req]) clr = 0xff0000;
req_str = Format("%s%s<c %x>%s</c>", req_str, req_sep, clr, requirement_names[req]);
req_sep = ", ";
}
msg = Format("$Requirements$: %s|", req_str);
}
else
{
msg = "";
}
// Info message: Cannot afford
if (entry.cost > GetWealth(GetOwner()))
{
msg = Format("%s<c ff0000>$Cost$: %d</c>", msg, entry.cost);
}
entry.message = msg;
return entry;
}
public func OnBuySelection(int callback_idx)
{
// Buy directly into cursor
var plr = GetOwner();
var cursor = GetCursor(plr);
if (!cursor) return false;
// Safety
var entry = base_material[callback_idx];
if (!entry) return false;
// Ignore if already have
var last_item = cursor->Contents(), item;
if (!last_item || (last_item->GetID() != entry.item))
{
// Requirements
if (entry.requirements)
for (var req in entry.requirements)
if (!techs[req])
return false;
// Cost
if (entry.cost)
{
if (GetWealth(plr) < entry.cost) return false;
DoWealth(plr, -entry.cost);
Sound("UI::Cash", true, nil, plr);
// Some items cost only once
if (entry.free_rebuy) entry.cost = nil;
}
else
{
// Still some feedback even on free selections
Sound("Liquids::Waterdrop1", true, nil, plr);
}
// Technology?
if (entry.callback)
{
// Teach technology
if (!Call(entry.callback, entry)) return false;
}
else
{
// Regular item: Buy into inventory
// Get rid of current item
if (last_item)
SellItem(last_item);
var item;
// Create item
if (!item) item = cursor->CreateContents(entry.item);
if (!item) return false; // ???
// for later sale
item.GidlValue = entry.cost;
// ammo up!
if (entry.ammo)
{
var ammo = item->CreateContents(entry.ammo);
if (!ammo) return false;
var stack_count = entry.stack_count;
if (stack_count < 0)
ammo->~SetInfiniteStackCount();
else if (stack_count > 0)
ammo->SetStackCount(stack_count);
}
// stack count
if (entry.infinite && item)
{
item->SetInfiniteStackCount();
}
}
}
// Buy only once? (all technologies without further upgrade tiers)
if (entry.remove_after_buy)
{
entry.hidden = true;
if (buy_menu) buy_menu->RemoveItem(callback_idx);
}
else if (entry.tech)
{
// Multi-tier tech upgrade
UpdateIndexedItem(callback_idx);
}
else
{
// Non-tech: Remember what has been bought
if (last_buy_idx != callback_idx)
{
var last_last_buy_idx = last_buy_idx;
last_buy_idx = callback_idx;
if (last_last_buy_idx >= 0) UpdateIndexedItem(last_last_buy_idx);
UpdateIndexedItem(last_buy_idx);
}
}
return true;
}
public func SellItem(item)
{
// Cash!
// Use custom value assigned by buy menu only
if (item.GidlValue)
{
DoWealth(GetOwner(), item.GidlValue);
Sound("UI::Cash", true, nil, GetOwner());
}
is_selling = true; // no item re-buy during sale
var success = item->RemoveObject();
is_selling = false;
return success;
}
// Makes an item available even though the requirements aren't yet met
public func SetItemAvailable(int entry_idx)
{
// Safety
var entry = base_material[entry_idx];
if (!entry) return false;
entry.requirements = nil;
entry.cost = nil;
return true;
}
public func OnOwnerChanged(new_owner)
{
if (buy_menu) buy_menu->SetOwner(new_owner);
return true;
}
// Callback from clonk or weapon: Ammo has been used up.
// Recharge from selected home base item if that option has been enabled
public func OnNoAmmo(object clonk)
{
if (is_selling) return false;
if (last_buy_idx < 0) return false;
var entry = base_material[last_buy_idx];
if (entry && entry.auto_rebuy)
{
ScheduleCall(this, this.OnBuySelection, 1, 1, last_buy_idx);
}
return false;
}
public func QuickBuyItem(id item)
{
// Find item in buy list
var entry, i=0;
for (entry in base_material)
if (entry.item == item)
break;
else
++i;
// If found, try to buy it
if (!OnBuySelection(i))
{
// TODO: Error sound
return false;
}
return true;
}
private func GainTechnology(proplist entry)
{
// Reach next tier
var tier = techs[entry.tech] + 1;
techs[entry.tech] = tier;
// For multi-tier-technologies, remove entry when last tier has been reached
if (entry.tiers) entry.remove_after_buy = (tier == entry.tiers);
// Technology gain callback.
Call(Format("~Gain%s", entry.tech), entry, tier);
// Update any related techs that may become available
var n = GetLength(base_material);
for (var i=0; i<n; ++i)
{
var req = base_material[i].requirements;
if (req && GetIndexOf(req, entry.tech) >= 0)
UpdateIndexedItem(i);
}
return true;
}
private func GainAdvancedWeapons(proplist entry, int tier)
{
// All done by requirements
return true;
}
private func GainLoadSpeed(proplist entry, int tier)
{
// Increase player's load speed
tech_load_speed_multiplier = [100, 40, 20, 1][tier];
// Update all current weapons
for (var weapon in FindObjects(Find_Owner(GetOwner()), Find_Func("Gidl_IsRangedWeapon")))
weapon->Gidl_UpdateLoadTimes();
return true;
}
private func GainShootingStrength(proplist entry, int tier)
{
// Increase player's shooting strength
// Weapon get plus x percent shooting strength
tech_shooting_strength_multiplier = [10, 20, 30, 40][tier];
// Update all current weapons
for (var weapon in FindObjects(Find_Owner(GetOwner()), Find_Func("Gidl_IsRangedWeapon")))
weapon->Guardians_UpdateShootingStrength();
return true;
}
private func GainLife(proplist entry, int tier)
{
// Increase player's life
tech_life = [1, 5, 10, 20][tier];
// Full refresh and max increase on current value
for (var clonk in FindObjects(Find_Owner(GetOwner()), Find_Func("IsClonk")))
{
clonk.MaxEnergy = clonk.Prototype.MaxEnergy * tech_life;
clonk->DoEnergy(clonk.MaxEnergy, true);
}
return true;
}