forked from Mirrors/openclonk
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
parent
c4fa92a6d2
commit
93ba7c5954
|
@ -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$";
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
[DefCore]
|
||||
id=Library_Vendor
|
||||
Version=7,0
|
||||
Category=C4D_StaticBack
|
|
@ -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, ...);
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
MsgBuy=Einkaufen
|
||||
MsgSell=Verkaufen
|
||||
YourWealth=Dein Kontostand
|
||||
MsgNotEnoughWealth=Kontostand zu gering!
|
|
@ -0,0 +1,4 @@
|
|||
MsgBuy=Buy Stuff
|
||||
MsgSell=Sell Stuff
|
||||
YourWealth=Your wealth
|
||||
MsgNotEnoughWealth=Not enough wealth!
|
|
@ -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$";
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue