Vendor Library

Extracted the buy menu of the flagpole to a library, so that other objects can implement a buy menu without having to duplicate the code. An overview of the changes follows:

Library_Base:
- Moved auto-sell functionality to Library_Vendor
- Moved buy and sell functionality to Library_Vendor
- Removed the old engine menu for buying and selling

Library_Vendor:
- Added proplist for accessing local variables, avoiding variable clashes
- Added interfaces GetBuyValue, GetBuyableItems, GetBuyableAmount, ChangeBuyableAmount, GetSellValue
- Changed DoBuy: it uses the interface functions, instead of hardcoded base material
- Changed / merged the existing sell functionality and the flagpole sell functionality in DoSell:
   * does not sell items if QueryOnSell returns true
   * sell sound only audible to seller
   * the sold object tries selling its contents first (for example a bow, if it ever were sellable. This may not hold in the settlement games, but adventures would allow it)
   * the sold object ejects any contents before it is removed. These are usually unsellable objects. Previously they would just be removed together with the object. It has to be tested whether this places the items on the ground or at the center of the sold object
   * removed the "sell all" functionality on right-click for the moment.
- Still has some functions from the old base selling: CanStack and GetSellableContents, for the auto-sell functionality
- Fixed a logical error in AllowBuyMenuEntries
- Distinguish between buyer/seller and payer more, so that the logic can easily be changed in one line later
- Allow for runtime overloads of interface functions
- Changed inconsistent variable naming to a more consistent one
- Added namespaces to all sounds, the "UnCash" sound still does not exist
- Added localization strings for insufficient wealth
- Fixed property name error (missing 'e')
- Buy menu is active by default

Flagpole:
- Replaced the custom sell functionality (how many of these do we have actually??) with the sell functionality from Library_Vendor
- Buying menu is allowed if the rule is active

System.ocg/Object.c
- global functions Buy and Sell ask the target if it is a vendor, instead of whether it is a base, and do the callback there.
- renamed the argument so that it is no longer called "base", but "vendor"
objectmenu
Mark 2016-01-23 00:07:17 +01:00
parent c4fa92a6d2
commit 93ba7c5954
7 changed files with 354 additions and 469 deletions

View File

@ -19,73 +19,9 @@ public func GetHealCost() { return 5;}
// Determines if the base can extinguish allied clonks
public func CanExtinguish() { return true; }
// The autosell function
public func ExecAutoSell()
{
// Search all objects for objects that want to be sold automatically
for(pObj in FindObjects(Find_Container(this), Find_Func("AutoSell")))
Sell(pObj->GetOwner(), pObj, this);
}
// Does the base block enemies?
public func CanBlockEnemies() { return true; }
// ---------------------------------------------------------
// ----------------- Settings for the trading of objects ----------------
// --- these functions can be overloaded for vendors or special bases ---
// returns an array with the definition, the amount
func GetBuyObject(int iIndex)
{
var aBuy = [0,0];
var idDef = GetBaseMaterial(GetOwner(), nil, iIndex, C4D_All);
aBuy[0] = idDef;
aBuy[1] = GetBaseMaterial(GetOwner(), idDef, 0);
if(!idDef) return nil;
// The default implementation returns the Basematerial of the playeer
return aBuy;
}
// returns an array with all buyable objects
func GetBuyObjects()
{
var aBuyObjects = [];
var iIndex, aBuy;
while(aBuy = GetBuyObject(iIndex++))
aBuyObjects[iIndex-1] = aBuy;
return aBuyObjects;
}
// returns the value of the object if sold in this base
func GetSellValue(object pObj)
{
// By default call the engine function
return pObj->GetValue();
}
func GetBuyValue(id idObj)
{
// By default call the engine function
return idObj->GetValue();
}
// change the amount of buyable material
func ChangeBaseMaterial(id idDef, int iCount)
{
// by default use base engine function
DoBaseMaterial(GetOwner(), idDef, iCount);
// this should also call UpdateClonkBuyMenus() if the standart function isn't used
}
public func OnBaseMaterialChange(id def, int change)
{
// and update the buy menu
UpdateClonkBuyMenus();
}
// ---------------------------------------------------------
// ------------- Base states -------------------------------
@ -128,22 +64,6 @@ func FxIntBaseTimer(pThis, effect, iTime)
if(CanExtinguish())
for(pObj in FindObjects(Find_Container(this), Find_OCF(OCF_OnFire), Find_Allied(GetOwner())))
pObj->Extinguish();
// Sell objects
ExecAutoSell();
// Update the sell menu of clonks (if someone knows a way to directly get info if the contents of the base change this coult be imporved)
if(aClonkSellList)
{
// Only if there are clonks with menus
var fFound;
for(pClonk in aClonkSellList)
if(pClonk)
{
fFound = 1;
break;
}
if(fFound)
UpdateSellList();
}
}
func FxIntBaseHealTimer(pClonk, effect)
@ -171,226 +91,4 @@ func FxIntBaseHealTimer(pClonk, effect)
}
}
// ------------------------ Buying -------------------------------------
local aClonkBuyList;
func AddClonkBuyList(object pClonk)
{
if(!aClonkBuyList) aClonkBuyList = [];
var iIndex = 0;
// find free slot
while(aClonkBuyList[iIndex] && aClonkBuyList[iIndex] != pClonk) iIndex++;
aClonkBuyList[iIndex] = pClonk;
}
func UpdateClonkBuyMenus()
{
if(!aClonkBuyList) aClonkBuyList = [];
// Reopen the menu for all clonks
var pClonk;
var iIndex;
for(pClonk in aClonkBuyList)
{
iIndex++;
if(!pClonk) continue;
if(pClonk->GetMenu() != Library_Base)
{
aClonkBuyList[iIndex-1] = 0;
continue;
}
OpenBuyMenu(pClonk, nil, pClonk->GetMenuSelection());
}
}
// Buy
func OpenBuyMenu(object pClonk, id idDef, int iSelection)
{
var aBuy = [0,0,0];
var iIndex, iSelection;
AddClonkBuyList(pClonk);
pClonk->CreateMenu (Library_Base, this, C4MN_Extra_Value, "$TxtNothingToBuy$", 0, C4MN_Style_Normal);
for(aBuy in GetBuyObjects())
{
if(aBuy[0] == idDef) iSelection = iIndex;
pClonk->AddMenuItem("$TxtBuy$", "BuyDummy", aBuy[0], aBuy[1], pClonk, nil, 128, 0, GetBuyValue(aBuy[0]));
iIndex++;
}
if(idDef || iSelection) pClonk->SelectMenuItem(iSelection);
}
func BuyDummy(id idDef, object pClonk, bool bRight, int iValue)
{
var iPlr = pClonk->GetOwner();
DoBuy(idDef, iPlr, GetOwner(), pClonk, bRight, 1);
OpenBuyMenu(pClonk, idDef);
}
func DoBuy(id idDef, int iForPlr, int iPayPlr, object pClonk, bool bRight, bool fShowErrors)
{
// Tries to buy an object or all available objects for bRight == true
// Returns the last bought object
var num_available = GetBaseMaterial(iPayPlr, idDef);
if(!num_available) return; //TODO
var num_buy = 1, pObj = nil;
if (bRight) num_buy = num_available;
while (num_buy--)
{
var iValue = GetBuyValue(idDef);
// Has the clonk enought money?
if(iValue > GetWealth(iPayPlr))
{
// TODO: get an errorsound
if(fShowErrors)
{
Sound("Error", 0, 100, iForPlr+1);
PlayerMessage(iForPlr, "$TxtNotEnoughtMoney$");
}
break;
}
// Take the cash
DoWealth(iPayPlr, -iValue);
Sound("UnCash", 0, 100, iForPlr+1); // TODO: get sound
// Decrease the Basematerial
ChangeBaseMaterial(idDef, -1);
// Deliver the object
var pObj = CreateContents(idDef);
pObj->SetOwner(iForPlr);
if(pObj->GetOCF() & OCF_CrewMember) pObj->MakeCrewMember(iForPlr);
if(pObj->GetOCF() & OCF_Collectible) pClonk->Collect(pObj);
}
return pObj;
}
// -------------------------- Selling -------------------------------------
func GetSellableContents()
{
return FindObjects(Find_Container(this), Find_Or(Find_Category(C4D_Object), Find_Category(C4D_Vehicle), Find_Category(65536/*C4D_TradeLiving*/)));
}
local aSellList;
local aClonkSellList;
func UpdateSellList()
{
aSellList = [];
var iIndex;
// Create a list of the sellable objects
for(pObj in GetSellableContents())
{
// Are we allowed to sell the object?
if (pObj.NoSell) continue;
// Only check the last item to stack, the engine normally sorts the objects so this should be enought to check
if(iIndex && CanStack(aSellList[iIndex-1][2], pObj))
aSellList[iIndex-1][1]++;
else
aSellList[iIndex++] = [pObj->GetID(), 1, pObj];
}
UpdateClonkSellMenus();
}
func AddClonkSellList(object pClonk)
{
if(!aClonkSellList) aClonkSellList = [];
var iIndex = 0;
// find free slot
while(aClonkSellList[iIndex] && aClonkSellList[iIndex] != pClonk) iIndex++;
aClonkSellList[iIndex] = pClonk;
}
func UpdateClonkSellMenus()
{
if(!aClonkSellList) return;
// Reopen the menu for all clonks
var pClonk;
var iIndex;
for(pClonk in aClonkSellList)
{
iIndex++;
if(!pClonk) continue;
if(pClonk->GetMenu() != BaseMaterial)
{
aClonkSellList[iIndex-1] = 0;
continue;
}
OpenSellMenu(pClonk, pClonk->GetMenuSelection(), 1);
}
}
// Sell Object
func OpenSellMenu(object pClonk, int iSelection, bool fNoListUpdate)
{
// Filled with [idDef, iCount, pObj] arrays
var aArray;
var iIndex;
if(!fNoListUpdate)
UpdateSellList();
AddClonkSellList(pClonk);
pClonk->CreateMenu (BaseMaterial, this, C4MN_Extra_Value, "$TxtNothingToSell$", 0, C4MN_Style_Normal, 1);
var iIndex;
for(aArray in aSellList) // aArray contains [idDef, iCount, pObj]
{
pClonk->AddMenuItem(Format("$TxtSell$", aArray[2]->GetName()), "SellDummy", aArray[0], aArray[1], pClonk, nil, 128+4, aArray[2], GetSellValue(aArray[2]));
iIndex++;
}
if(iSelection == iIndex) iSelection--;
pClonk->SelectMenuItem(iSelection);
}
func CanStack(object pFirst, object pSecond)
{
// Test if these Objects differ from each other
if(!pFirst->CanConcatPictureWith(pSecond)) return false;
if(GetSellValue(pFirst) != GetSellValue(pSecond)) return false;
// ok they can be stacked
return true;
}
func SellDummy(id idDef, object pClonk, bool bRight)
{
var iIndex = pClonk->GetMenuSelection();
var aArray = aSellList[iIndex];
DoSell(aArray[2], GetOwner(), bRight);
OpenSellMenu(pClonk, iIndex);
}
func DoSell(object pObj, int iPlr, bool bRight)
{
// Test the object
if(pObj.NoSell || pObj->GetOCF() & OCF_CrewMember)
{
// Enter base (needed for NoSell objects in other objects which are sold)
if(pObj->Contained() != this)
pObj->Enter(this);
return 0;
}
// Sell contents first
var pContents;
for(pContents in FindObjects(Find_Container(pObj)))
DoSell(pContents, iPlr);
// Give the player the cash
DoWealth(iPlr, GetSellValue(pObj));
Sound("UI::Cash", 0, 100, iPlr+1); // TODO: get sound
// right clicked? then sell other objects too
var pNewObj;
var bFound;
if(bRight)
{
for(var pNewObj in GetSellableContents())
if(CanStack(pObj, pNewObj) && pObj != pNewObj)
{
bFound = 1;
break;
}
}
// OnSale callback to object e.g. for goal callbacks
pObj->~OnSale(iPlr, this);
// And remove the object
pObj->RemoveObject();
if(bFound) return DoSell(pNewObj, iPlr, bRight); // wtf why is this recursive? will fail with too many objects
return true;
}
local Name = "$Name$";

View File

@ -0,0 +1,4 @@
[DefCore]
id=Library_Vendor
Version=7,0
Category=C4D_StaticBack

View File

@ -0,0 +1,322 @@
/**
Structure Vendor
Basic library for structures, handles:
* Selling objects in interaction menu
@author Randrian, Marky (buy and sell logic), Maikel (menu)
*/
local lib_vendor = {}; // proplist that avoids clashes in variables
// ----------------- Settings for the trading of objects ----------------
// --- these functions can be overloaded for vendors or special bases ---
// ----- Buying
public func AllowBuyMenuEntries(){ return true;}
func GetBuyValue(id item)
{
// By default call the engine function
return item->GetValue();
}
func GetBuyableItems(int for_player)
{
// By default get the base material
var i, item;
var items = [];
while (item = GetBaseMaterial(for_player, nil, i++))
{
PushBack(items, item);
}
return items;
}
func GetBuyableAmount(int for_player, id item)
{
// by default use the base material
return GetBaseMaterial(for_player, item);
}
func ChangeBuyableAmount(int for_player, id item, int amount)
{
// by default use base engine function
DoBaseMaterial(for_player, item, amount);
}
// ----- Selling
// returns the value of the object if sold in this base
func GetSellValue(object item)
{
// By default call the engine function
return item->GetValue();
}
// -------------------------- Internal functions --------------------------
// --- these functions should not be overloaded,
// --- but offer more interfaces if necessary
// ------------------------ Buying -------------------------------------
func DoBuy(id item, int for_player, int wealth_player, object buyer, bool buy_all_available, bool show_errors)
{
// Tries to buy an object or all available objects for bRight == true
// Returns the last bought object
var num_available = this->GetBuyableAmount(wealth_player, item);
if(!num_available) return; //TODO
var num_buy = 1, purchased = nil;
if (buy_all_available) num_buy = num_available;
while (num_buy--)
{
var price = this->GetBuyValue(item);
// Does the player have enough money?
if(price > GetWealth(wealth_player))
{
if(show_errors)
{
Sound("UI::Error", nil, nil, for_player + 1);
PlayerMessage(for_player, "$MsgNotEnoughWealth$");
}
break;
}
// Take the cash
DoWealth(wealth_player, -price);
Sound("UI::UnCash", nil, nil, for_player + 1); // TODO: get sound
// Decrease the base material, allow runtime overload
this->ChangeBuyableAmount(wealth_player, item, -1);
// Deliver the object
purchased = CreateContents(item);
purchased->SetOwner(for_player);
if (purchased->GetOCF() & OCF_CrewMember) purchased->MakeCrewMember(for_player);
if (purchased->GetOCF() & OCF_Collectible) buyer->Collect(purchased);
}
return purchased;
}
// -------------------------- Selling -------------------------------------
func DoSell(object obj, int wealth_player)
{
if (obj->~QueryOnSell(wealth_player)) return;
// Sell contents first
for(var contents in FindObjects(Find_Container(obj)))
{
DoSell(contents, wealth_player);
}
// Give the player the cash
DoWealth(wealth_player, this->GetSellValue(obj));
Sound("UI::Cash", nil, nil, wealth_player + 1);
// OnSale callback to object e.g. for goal updates
obj->~OnSale(wealth_player, this);
// Remove object, but eject contents
// ejecting contents may be important, because those contents
// that return true in QueryOnSell are still in the object
// and they should not be removed (why else would they have QueryOnSell)?
if (obj) obj->RemoveObject(true);
return true;
}
// -------------------------- Vendor functionality -------------------------------------
func IsVendor()
{
return lib_vendor.is_vendor;
}
// Makes this building a vendor or removes the base functionallity
public func MakeVendor(bool should_be_vendor)
{
if (should_be_vendor)
{
if (!lib_vendor.is_vendor)
lib_vendor.is_vendor = AddEffect("IntVendor", this, 1, 10, this);
}
else
{
if (lib_vendor.is_vendor)
RemoveEffect(nil, nil, lib_vendor.vendor);
lib_vendor.is_vendor = nil;
}
}
func FxIntVendorTimer(object target, proplist effect, int time)
{
// Search all objects for objects that want to be sold automatically
for (var item in FindObjects(Find_Container(this), Find_Func("AutoSell")))
Sell(item->GetOwner(), item, this);
}
// -------------------------- Menus -------------------------------------
// ----- generic things
// Provides an interaction menu for buying things.
public func HasInteractionMenu() { return true; }
public func GetInteractionMenus(object clonk)
{
var menus = _inherited() ?? [];
// only open the menus if ready
if (this->AllowBuyMenuEntries())
{
var buy_menu =
{
title = "$MsgBuy$",
entries_callback = this.GetBuyMenuEntries,
callback = "OnBuyMenuSelection",
callback_target = this,
BackgroundColor = RGB(50, 50, 0),
Priority = 20
};
PushBack(menus, buy_menu);
}
return menus;
}
func GetBuyMenuEntry(int index, id item, int amount, int value)
{
var entry =
{
Right = "4em", Bottom = "2em",
BackgroundColor = {Std = 0, OnHover = 0x50ff0000},
image = {Right = "2em", Style = GUI_TextBottom | GUI_TextRight},
price = {Left = "2em", Priority = 3}
};
entry.image.Symbol = item;
entry.image.Text = Format("%dx", amount);
entry.price.Text = Format("<c ffff00>%d{{Icon_Wealth}}</c>", value);
entry.Priority = 1000 * value + index; // Order by value and then by BaseMaterial index.
return entry;
}
// ----- buying
public func GetBuyMenuEntries(object clonk)
{
// We need to know when exactly we should refresh the menu to prevent unecessary refreshs.
var lowest_greyed_out_price = nil;
// distinguish owners here. at the moment they are the same, but this may change
var wealth_player = GetOwner();
var for_player = GetOwner();
var wealth = GetWealth(wealth_player);
var menu_entries = [];
var i = 0, item, amount;
for (item in this->GetBuyableItems(for_player))
{
amount = this->GetBuyableAmount(for_player, item);
var value = this->GetBuyValue(item);
var entry = GetBuyMenuEntry(i, item, amount, value);
if (value > wealth) // If the player can't afford it, the item (except for the price) is overlayed by a greyish color.
{
entry.overlay = {Priority = 2, BackgroundColor = RGBa(50, 50, 50, 150)};
if (lowest_greyed_out_price == nil || value < lowest_greyed_out_price)
lowest_greyed_out_price = value;
}
PushBack(menu_entries, {symbol = item, extra_data = nil, custom = entry});
}
// At the top of the menu, we add the player's wealth.
var entry =
{
Bottom = "1.1em",
BackgroundColor = RGBa(100, 100, 50, 100),
Priority = -1,
left_text =
{
Style = GUI_TextVCenter | GUI_TextLeft,
Text = "<c 888888>$YourWealth$:</c>"
},
right_text =
{
Style = GUI_TextVCenter | GUI_TextRight,
Text = Format("<c ffff00>%d{{Icon_Wealth}}</c>", wealth)
}
};
var fx = AddEffect("UpdateWealthDisplay", this, 1, 5, nil, GetID());
fx.lowest_greyed_out_price = lowest_greyed_out_price;
fx.last_wealth = wealth;
fx.plr = wealth_player;
PushBack(menu_entries, {symbol = nil, extra_data = nil, custom = entry, fx = fx});
return menu_entries;
}
public func OnBuyMenuSelection(id def, extra_data, object clonk)
{
// distinguish owners
var wealth_player = GetOwner();
var for_player = clonk->GetController();
// Buy
DoBuy(def, for_player, wealth_player, clonk);
// Excess objects exit flag (can't get them out...)
var i = ContentsCount();
var obj;
while (i--)
if (obj = Contents(i))
{
obj->Exit(0, GetDefHeight() / 2);
// newly bought items do not fade out until they've been collected once
if (obj && ObjectCount(Find_ID(Rule_ObjectFade)) && !obj.HasNoFadeOut)
{
obj.HasNoFadeOut = this.BuyItem_HasNoFadeout;
obj.BuyOverload_Entrance = obj.Entrance;
obj.Entrance = this.BuyItem_Entrance;
}
}
UpdateInteractionMenus(this.GetBuyMenuEntries);
}
// ----- Menu updates, misc
private func FxUpdateWealthDisplayTimer(object target, effect fx, int time)
{
if (!fx.menu_target) return -1;
if (fx.last_wealth == GetWealth(fx.wealth_player)) return FX_OK;
fx.last_wealth = GetWealth(fx.wealth_player);
// Do we need a full refresh? New objects might have become available.
if (fx.lowest_greyed_out_price && fx.lowest_greyed_out_price <= fx.last_wealth)
{
target->UpdateInteractionMenus(target.GetBuyMenuEntries);
return FX_OK;
}
// Just update the money display otherwise.
GuiUpdate({right_text = {Text = Format("<c ffff00>%d{{Icon_Wealth}}</c>", fx.last_wealth)}}, fx.main_ID, fx.ID, fx.menu_target);
return FX_OK;
}
public func FxUpdateWealthDisplayOnMenuOpened(object target, effect fx, int main_ID, int ID, object subwindow_target)
{
fx.main_ID = main_ID;
fx.menu_target = subwindow_target;
fx.ID = ID;
}
// newly bought items do not fade out unless collected
func BuyItem_HasNoFadeout() { return true; }
func BuyItem_Entrance()
{
// after first collection, fade out rule should be effective again
var overloaded_fn = this.BuyOverload_Entrance;
this.HasNoFadeOut = nil;
this.BuyOverload_Entrance = nil;
this.Entrance = overloaded_fn;
if (overloaded_fn) return Call(overloaded_fn, ...);
}

View File

@ -0,0 +1,4 @@
MsgBuy=Einkaufen
MsgSell=Verkaufen
YourWealth=Dein Kontostand
MsgNotEnoughWealth=Kontostand zu gering!

View File

@ -0,0 +1,4 @@
MsgBuy=Buy Stuff
MsgSell=Sell Stuff
YourWealth=Your wealth
MsgNotEnoughWealth=Not enough wealth!

View File

@ -5,6 +5,7 @@
#include Library_Flag
#include Library_Base // Needed for DoBuy...
#include Library_PowerDisplay
#include Library_Vendor
local ActMap = {
Fly = {
@ -39,150 +40,8 @@ public func NoConstructionFlip() { return true; }
// The flag can take valuables which are then auto-sold.
public func IsContainer() { return true; }
public func GetInteractionMenus(object clonk)
{
var menus = _inherited() ?? [];
// only open the menus if ready
if (ObjectCount(Find_ID(Rule_BuyAtFlagpole)))
{
var buy_menu =
{
title = "$MsgBuy$",
entries_callback = this.GetBuyMenuEntries,
callback = "OnBuyMenuSelection",
callback_target = this,
BackgroundColor = RGB(50, 50, 0),
Priority = 20
};
PushBack(menus, buy_menu);
}
return menus;
}
public func GetBuyMenuEntries(object clonk)
{
// default design of a control menu item
var custom_entry =
{
Right = "4em", Bottom = "2em",
BackgroundColor = {Std = 0, OnHover = 0x50ff0000},
image = {Right = "2em", Style = GUI_TextBottom | GUI_TextRight},
price = {Left = "2em", Priority = 3}
};
// We need to know when exactly we should refresh the menu to prevent unecessary refreshs.
var lowest_greyed_out_price = nil;
var wealth_player = GetOwner(); // Note that the flag owner pays for everything atm.
var wealth = GetWealth(wealth_player);
var menu_entries = [];
var i = 0, item, amount;
while (item = GetBaseMaterial(GetOwner(), nil, i++))
{
amount = GetBaseMaterial(GetOwner(), item);
var entry =
{
Prototype = custom_entry,
image = {Prototype = custom_entry.image},
price = {Prototype = custom_entry.price}
};
entry.image.Symbol = item;
entry.image.Text = Format("%dx", amount);
var value = GetBuyValue(item);
entry.price.Text = Format("<c ffff00>%d{{Icon_Wealth}}</c>", value);
entry.Priority = 1000 * value + i; // Order by value and then by BaseMaterial index.
if (value > wealth) // If the player can't afford it, the item (except for the price) is overlayed by a greyish color.
{
entry.overlay = {Priority = 2, BackgroundColor = RGBa(50, 50, 50, 150)};
if (lowest_greyed_out_price == nil || value < lowest_greyed_out_price)
lowest_greyed_out_price = value;
}
PushBack(menu_entries, {symbol = item, extra_data = nil, custom = entry});
}
// At the top of the menu, we add the player's wealth.
var entry =
{
Bottom = "1.1em",
BackgroundColor = RGBa(100, 100, 50, 100),
Priority = -1,
left_text =
{
Style = GUI_TextVCenter | GUI_TextLeft,
Text = "<c 888888>$YourWealth$:</c>"
},
right_text =
{
Style = GUI_TextVCenter | GUI_TextRight,
Text = Format("<c ffff00>%d{{Icon_Wealth}}</c>", wealth)
}
};
var fx = AddEffect("UpdateWealthDisplay", this, 1, 5, nil, GetID());
fx.lowest_greyed_out_price = lowest_greyed_out_price;
fx.last_wealth = wealth;
fx.plr = wealth_player;
PushBack(menu_entries, {symbol = nil, extra_data = nil, custom = entry, fx = fx});
return menu_entries;
}
public func OnBuyMenuSelection(id def, extra_data, object clonk)
{
// Buy
DoBuy(def, clonk->GetController(), GetOwner(), clonk);
// Excess objects exit flag (can't get them out...)
var i = ContentsCount();
var obj;
while (i--)
if (obj = Contents(i))
{
obj->Exit(0, GetDefHeight() / 2);
// newly bought items do not fade out until they've been collected once
if (obj && ObjectCount(Find_ID(Rule_ObjectFade)) && !obj.HasNoFadeOut)
{
obj.HasNoFadeOut = this.BuyItem_HasNoFadeout;
obj.BuyOverload_Entrance = obj.Entrance;
obj.Entrance = this.BuyItem_Entrance;
}
}
UpdateInteractionMenus(this.GetBuyMenuEntries);
}
private func FxUpdateWealthDisplayTimer(object target, effect fx, int time)
{
if (!fx.menu_target) return -1;
if (fx.last_wealth == GetWealth(fx.wealth_player)) return FX_OK;
fx.last_wealth = GetWealth(fx.wealth_player);
// Do we need a full refresh? New objects might have become available.
if (fx.lowest_greyed_out_price && fx.lowest_greyed_out_price <= fx.last_wealth)
{
target->UpdateInteractionMenus(target.GetBuyMenuEntries);
return FX_OK;
}
// Just update the money display otherwise.
GuiUpdate({right_text = {Text = Format("<c ffff00>%d{{Icon_Wealth}}</c>", fx.last_wealth)}}, fx.main_ID, fx.ID, fx.menu_target);
return FX_OK;
}
public func FxUpdateWealthDisplayOnMenuOpened(object target, effect fx, int main_ID, int ID, object subwindow_target)
{
fx.main_ID = main_ID;
fx.menu_target = subwindow_target;
fx.ID = ID;
}
// newly bought items do not fade out unless collected
func BuyItem_HasNoFadeout() { return true; }
func BuyItem_Entrance()
{
// after first collection, fade out rule should be effective again
var overloaded_fn = this.BuyOverload_Entrance;
this.HasNoFadeOut = nil;
this.BuyOverload_Entranc = nil;
this.Entrance = overloaded_fn;
if (overloaded_fn) return Call(overloaded_fn, ...);
}
// Allow buying only if the rule is active
public func AllowBuyMenuEntries(){ return ObjectCount(Find_ID(Rule_BuyAtFlagpole));}
public func RejectCollect(id def, object obj)
{
@ -196,11 +55,7 @@ public func Collection(object obj)
{
if (obj->~IsValuable() && !obj->~QueryOnSell(obj->GetController()))
{
DoWealth(obj->GetController(), obj->GetValue());
Sound("UI::Cash");
// OnSale callback to object e.g. for goal updates
obj->~OnSale(obj->GetController(), this);
if (obj) obj->RemoveObject();
DoSell(obj, obj->GetController());
}
return _inherited(obj, ...);
}
@ -255,8 +110,6 @@ public func SaveScenarioObject(props)
/*-- Properties --*/
// Provides an interaction menu for buying things.
public func HasInteractionMenu() { return true; }
local Name = "$Name$";
local Description = "$Description$";

View File

@ -340,26 +340,26 @@ global func RootSurface()
}
}
// Buys an object.
global func Buy (id idBuyObj, int iForPlr, int iPayPlr, object pToBase, bool fShowErrors)
// Buys an object. Returns the object if it could be bought.
global func Buy (id idBuyObj, int iForPlr, int iPayPlr, object pFromVendor, bool fShowErrors)
{
// if no base is given try this
if(!pToBase) pToBase = this;
// not a base?
if( !pToBase->~IsBase() )
return 0;
return pToBase->DoBuy(idBuyObj, iForPlr, iPayPlr, 0, 0, fShowErrors);
// if no vendor is given try this
if (!pFromVendor) pFromVendor = this;
// not a vendor?
if (!pFromVendor->~IsVendor())
return nil;
return pFromVendor->DoBuy(idBuyObj, iForPlr, iPayPlr, 0, 0, fShowErrors);
}
// Sells an object.
global func Sell (int iPlr, object pObj, object pToBase)
// Sells an object. Returns true if it could be sold.
global func Sell (int iPlr, object pObj, object pToVendor)
{
// if no base is given try this
if(!pToBase) pToBase = this;
// not a base?
if( !pToBase->~IsBase() )
return 0;
return pToBase->DoSell(pObj, iPlr);
// if no vendor is given try this
if(!pToVendor) pToVendor = this;
// not a vendor?
if (!pToVendor->~IsVendor())
return false;
return pToVendor->DoSell(pObj, iPlr);
}
// Returns the owner if this is a base.