forked from Mirrors/openclonk
Simplified logic for substitutes
The production cost method returns an array of material cost arrays, but their format has changed. Instead of [<default resource>, <cost>, <substitute(s)>] it now returns an array of proplists [{Resource = optionA, Amount = costA}, {Resource = optionB, Amount = costB}] so that there are fewer cases to keep in mind: No "are there substitutes?", but you can simply check all variants and pick the best. This also allows different substitutions, for example "2 earth or 3 sand" is now possible.master
parent
36dbbd27e1
commit
cf7469043d
|
@ -180,44 +180,35 @@ public func OnProductHover(symbol, extra_data, desc_menu_target, menu_id)
|
|||
|
||||
var product_id = symbol;
|
||||
var costs = ProductionCosts(product_id);
|
||||
var cost_msg = "";
|
||||
for (var comp in costs)
|
||||
var cost_message = "";
|
||||
for (var cost_options in costs)
|
||||
{
|
||||
if (GetLength(cost_msg))
|
||||
if (GetLength(cost_options) == 0) // Must be an array with entries
|
||||
{
|
||||
cost_msg = Format("%s +", cost_msg);
|
||||
continue;
|
||||
}
|
||||
if (!comp[2])
|
||||
if (GetLength(cost_message))
|
||||
{
|
||||
cost_msg = Format("%s %s {{%i}}", cost_msg, GetCostString(comp[1], CheckComponent(comp[0], comp[1])), comp[0]);
|
||||
cost_message = Format("%s +", cost_message);
|
||||
}
|
||||
else
|
||||
|
||||
// Append the original resource cost
|
||||
cost_message = Format("%s %s {{%i}}", cost_message, GetCostString(cost_options[0].Amount, CheckComponent(cost_options[0])), cost_options[0].Resource);
|
||||
// Append all of its substitutes
|
||||
for (var i = 1; i < GetLength(cost_options); ++i)
|
||||
{
|
||||
if (GetType(comp[2]) == C4V_Array)
|
||||
{
|
||||
cost_msg = Format("%s (%s {{%i}}", cost_msg, GetCostString(comp[1], CheckComponent(comp[0], comp[1])), comp[0]);
|
||||
for (var subs in comp[2])
|
||||
{
|
||||
cost_msg = Format("%s / %s {{%i}}", cost_msg, GetCostString(comp[1], CheckComponent(subs, comp[1])), subs);
|
||||
}
|
||||
cost_msg = Format("%s)", cost_msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
cost_msg = Format("%s (%s {{%i}} / %s {{%i}})", cost_msg, GetCostString(comp[1], CheckComponent(comp[0], comp[1])), comp[0], GetCostString(comp[1], CheckComponent(comp[2], comp[1])), comp[2]);
|
||||
//cost_msg = Format("%s %s ({{%i}} / {{%i}})", cost_msg, GetCostString(comp[1], CheckComponent(comp[0], comp[1])), comp[0], comp[2]);
|
||||
}
|
||||
cost_message = Format("%s / %s {{%i}}", cost_message, GetCostString(cost_options[i].Amount, CheckComponent(cost_options[i])), cost_options[i].Resource);
|
||||
}
|
||||
}
|
||||
if (this->~FuelNeed(product_id))
|
||||
{
|
||||
cost_msg = Format("%s %s {{Icon_Producer_Fuel}}", cost_msg, GetCostString(1, CheckFuel(product_id)));
|
||||
cost_message = Format("%s %s {{Icon_Producer_Fuel}}", cost_message, GetCostString(1, CheckFuel(product_id)));
|
||||
}
|
||||
if (this->~PowerNeed(product_id))
|
||||
{
|
||||
cost_msg = Format("%s + {{Library_PowerConsumer}}", cost_msg);
|
||||
cost_message = Format("%s + {{Library_PowerConsumer}}", cost_message);
|
||||
}
|
||||
new_box.requirements.Text = cost_msg;
|
||||
new_box.requirements.Text = cost_message;
|
||||
GuiUpdate(new_box, menu_id, 1, desc_menu_target);
|
||||
}
|
||||
|
||||
|
@ -336,22 +327,80 @@ public func GetProducts(object for_clonk)
|
|||
|
||||
/**
|
||||
Determines the production costs for an item.
|
||||
|
||||
For each component of the item the function returns an array
|
||||
of the original component definition and its possible substitutes.
|
||||
Substitutes can be defined in the product, by the callback:
|
||||
|
||||
Callback to product ID:
|
||||
- GetSubstituteComponent(id component_id, int amount)
|
||||
- the function may return one of the following:
|
||||
* an ID; in this case it is assumed, that the component
|
||||
can be substituted with this ID and the original amount
|
||||
* a proplist {Resource = substitude_id, Amount = amount};
|
||||
in this case that proplist defines the new amount.
|
||||
* an array of either ID or proplist as above (can be mixed, but this is not recommended)
|
||||
for multiple substitute options.
|
||||
|
||||
@param item_id id of the item under consideration.
|
||||
@return a list of objects and their respective amounts.
|
||||
The list is an array of arrays. Each entry in the list
|
||||
is an array of proplists {Resource = id, Amount = int}
|
||||
for the options, e.g. [{Resource = Ore, Amount = 2}, {Resource = Metal, Amount = 1}].
|
||||
*/
|
||||
public func ProductionCosts(id item_id)
|
||||
{
|
||||
/* NOTE: This may be overloaded by the producer */
|
||||
var comp_list = [];
|
||||
var comp_id, index = 0;
|
||||
while (comp_id = item_id->GetComponent(nil, index))
|
||||
var component_list = [];
|
||||
var component_id, index = 0;
|
||||
while (component_id = item_id->GetComponent(nil, index))
|
||||
{
|
||||
var amount = item_id->GetComponent(comp_id);
|
||||
var substitute = item_id->~GetSubstituteComponent(comp_id);
|
||||
comp_list[index] = [comp_id, amount, substitute];
|
||||
var amount = item_id->GetComponent(component_id);
|
||||
var options = [ProductionCostData(component_id, amount)];
|
||||
var substitute = item_id->~GetSubstituteComponent(component_id, amount);
|
||||
if (GetType(substitute) == C4V_Array)
|
||||
{
|
||||
for (var option in substitute)
|
||||
{
|
||||
var data = ProductionCostData(option, amount);
|
||||
if (data)
|
||||
{
|
||||
PushBack(options, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var data = ProductionCostData(substitute, amount);
|
||||
if (data)
|
||||
{
|
||||
PushBack(options, data);
|
||||
}
|
||||
}
|
||||
component_list[index] = options;
|
||||
index++;
|
||||
}
|
||||
return comp_list;
|
||||
return component_list;
|
||||
}
|
||||
|
||||
|
||||
func ProductionCostData(component, int amount)
|
||||
{
|
||||
// Assume that it already has the correct format
|
||||
if (GetType(component) == C4V_PropList)
|
||||
{
|
||||
return component;
|
||||
}
|
||||
// The actual format
|
||||
else if (GetType(component) == C4V_Def)
|
||||
{
|
||||
return {Resource = component, Amount = amount};
|
||||
}
|
||||
// Invalid value
|
||||
else
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -609,48 +658,30 @@ func CheckComponents(id product, bool remove)
|
|||
{
|
||||
for (var item in ProductionCosts(product))
|
||||
{
|
||||
var mat_id = item[0];
|
||||
var mat_cost = item[1];
|
||||
var mat_substitute = item[2];
|
||||
if (!CheckComponent(mat_id, mat_cost))
|
||||
// No cost specified?
|
||||
if (GetLength(item) == 0)
|
||||
{
|
||||
if (mat_substitute)
|
||||
return false;
|
||||
}
|
||||
var mat_id;
|
||||
var mat_cost;
|
||||
|
||||
var found = false;
|
||||
// Check all possible resource options
|
||||
for (var option in item)
|
||||
{
|
||||
if (CheckComponent(option))
|
||||
{
|
||||
if (GetType(mat_substitute) == C4V_Array)
|
||||
{
|
||||
var found = false;
|
||||
for (var substitute in mat_substitute) // Check all possible substitutes
|
||||
{
|
||||
if (CheckComponent(substitute, mat_cost))
|
||||
{
|
||||
mat_id = substitute;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
return false; // Substitutes missing.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check substitute components
|
||||
if (CheckComponent(mat_substitute, mat_cost))
|
||||
{
|
||||
mat_id = mat_substitute;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false; // Substitute missing.
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false; // Components missing.
|
||||
mat_id = option.Resource;
|
||||
mat_cost = option.Amount;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
return false; // Resources are missing.
|
||||
}
|
||||
if (remove)
|
||||
{
|
||||
for (var i = 0; i < mat_cost; i++)
|
||||
|
@ -693,9 +724,21 @@ public func GetAvailableComponentAmount(id material)
|
|||
}
|
||||
|
||||
|
||||
public func CheckComponent(id component, int amount)
|
||||
public func CheckComponent(component, int amount)
|
||||
{
|
||||
return GetAvailableComponentAmount(component) >= amount;
|
||||
if (GetType(component) == C4V_Def)
|
||||
{
|
||||
return GetAvailableComponentAmount(component) >= amount;
|
||||
}
|
||||
else if (GetType(component) == C4V_PropList)
|
||||
{
|
||||
return CheckComponent(component.Resource, component.Amount);
|
||||
}
|
||||
else
|
||||
{
|
||||
FatalError(Format("Unsupported parameter format: %v", component));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -935,8 +978,14 @@ public func RequestAllMissingComponents(proplist product)
|
|||
// Request all currently unavailable components.
|
||||
for (var item in ProductionCosts(item_id))
|
||||
{
|
||||
var mat_id = item[0];
|
||||
var mat_cost = item[1];
|
||||
// Must actually define items
|
||||
if (GetLength(item) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// TODO: Smartly request resources? Currently it will only request the first of the possible options (the default components)
|
||||
var mat_id = item[0].Resource;
|
||||
var mat_cost = item[0].Amount;
|
||||
// No way to request liquids currently, player must use pumps instead.
|
||||
if (mat_id->~IsLiquid())
|
||||
{
|
||||
|
|
|
@ -427,11 +427,11 @@ global func Test5_OnStart(int plr)
|
|||
|
||||
Log("Testing the behaviour of ProductionCosts()");
|
||||
|
||||
passed &= doTest("Costs for single component object (Metal). Got %v, expected %v.", producer->ProductionCosts(Metal), [[Ore, 1, nil]]);
|
||||
passed &= doTest("Costs for multi component object (Pickaxe). Got %v, expected %v.", producer->ProductionCosts(Pickaxe), [[Metal, 1, nil], [Wood, 1, nil]]);
|
||||
passed &= doTest("Costs for object with liquid and fuel need (Bread). Got %v, expected %v.", producer->ProductionCosts(Bread), [[Flour, 1, nil], [Water, 50, nil]]);
|
||||
passed &= doTest("Costs for object with a single substitute component (Loam). Got %v, expected %v.", producer->ProductionCosts(Loam), [[Earth, 2, Sand], [Water, 60, nil]]);
|
||||
passed &= doTest("Costs for object with multiple substitute components (TeleGlove). Got %v, expected %v.", producer->ProductionCosts(TeleGlove), [[Diamond, 1, [Ruby, Amethyst]], [Metal, 2, nil]]);
|
||||
passed &= doTest("Costs for single component object (Metal). Got %v, expected %v.", producer->ProductionCosts(Metal), [[{Resource = Ore, Amount = 1}]]);
|
||||
passed &= doTest("Costs for multi component object (Pickaxe). Got %v, expected %v.", producer->ProductionCosts(Pickaxe), [[{Resource = Metal, Amount = 1}], [{Resource = Wood, Amount = 1}]]);
|
||||
passed &= doTest("Costs for object with liquid and fuel need (Bread). Got %v, expected %v.", producer->ProductionCosts(Bread), [[{Resource = Flour, Amount = 1}], [{Resource = Water, Amount = 50}]]);
|
||||
passed &= doTest("Costs for object with a single substitute component (Loam). Got %v, expected %v.", producer->ProductionCosts(Loam), [[{Resource = Earth, Amount = 2}, {Resource = Sand, Amount = 2}], [{Resource = Water, Amount = 60}]]);
|
||||
passed &= doTest("Costs for object with multiple substitute components (TeleGlove). Got %v, expected %v.", producer->ProductionCosts(TeleGlove), [[{Resource = Diamond, Amount = 1}, {Resource = Ruby, Amount = 1}, {Resource = Amethyst, Amount = 1}], [{Resource = Metal, Amount = 2}]]);
|
||||
|
||||
producer->RemoveObject();
|
||||
return passed;
|
||||
|
@ -974,7 +974,16 @@ global func doTest(description, returned, expected)
|
|||
return passed;
|
||||
}
|
||||
|
||||
var test = (returned == expected);
|
||||
var test;
|
||||
if (GetType(returned) == C4V_PropList
|
||||
|| GetType(expected) == C4V_PropList)
|
||||
{
|
||||
test = DeepEqual(returned, expected);
|
||||
}
|
||||
else
|
||||
{
|
||||
test = (returned == expected);
|
||||
}
|
||||
|
||||
var predicate = "[Fail]";
|
||||
if (test) predicate = "[Pass]";
|
||||
|
|
Loading…
Reference in New Issue