1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
|
/*--
Stackable
Author: Newton
Including this object means, the object is stackable. Other objects of
the same type will be added automatically to the object. This functionality
is similar to the Pack-functionality of the arrows in old clonk titles only
more general.
The count of how many objects are stacked together (into a single one, this
one) is shown in the picture of the object and can be queried and set via
GetStackCount()/SetStackCount().
To take one object of the stack, call TakeObject(). As long
as the object exists, one can always take an object, even if it is the last
one (self). This object is always outside.
Infinite stackable count can by achieved using SetInfiniteStackCount.
On entrance (or to be more precise: on RejectEntrance), it will be checked
if the entering stackable object can be distributed over the other objects
of the same ID. If yes, this object is deleted and the other object(s) will
have a higher stack-count.
Example 1:
'15x Arrow' is about to enter a clonk which has '5x Arrow'. 15 will be added
to the stack-count of the clonks '5x Arrow'-object (making it '20x Arrow'),
the entering object will be deleted.
Example 2:
'17x Arrow' is about to enter a clonk which has '15x Arrow' and a bow with
'10x Arrow' in it's ammunition slot. 10 will be added to the stack-count
of the arrows in the bow, 5 to the stack-count of the arrows in the clonk
(assuming MaxStackCount() is 20) and the original arrows-object will have
2 arrows left. If there is an inventory slot left, the '2x Arrow" object
will enter the clonk.
Most objects which can be stacked might want to set different pictures
and ingame graphics for different counts of objects. This can be done
by overloading UpdatePicture(), but remember to write _inherited(...) then.
--*/
local count, count_is_infinite;
// Max size of stack
static const Stackable_Max_Count = 2147483647;
// What GetStackCount should return if the count is set to infinite
// Set this to a fairly large number and not e.g. -1, so naive
// implementations that update their graphics by GetStackCount() show a
// bunch of items. However, the number shouldn't be too large so the
// object doesn't get overly heavy.
// Note that count_is_infinite is a separate variable, so we can support
// stacks >999 but <Inf in the future.
static const Stackable_Infinite_Count = 50;
public func IsStackable() { return true; }
public func GetStackCount() { return count; }
public func MaxStackCount() { return 20; }
public func InitialStackCount() { return MaxStackCount(); }
public func IsFullStack() { return this->IsInfiniteStackCount() || (GetStackCount() >= MaxStackCount()); }
public func IsInfiniteStackCount() { return !!count_is_infinite; }
protected func Construction()
{
count = InitialStackCount();
return _inherited(...);
}
func Destruction()
{
NotifyContainer();
return _inherited(...);
}
/**
* Puts the stack count of another object on top of this stack.
* The stack count of the other object is not modified.
* @par other the other object. Must be of the same ID as the stack.
* @return the amount of objects that could be stacked.
*/
public func Stack(object other)
{
if (other->GetID() != GetID()) return 0;
if (other == this) return 0;
// Infinite stacks can always take everything
if (this->IsInfiniteStackCount()) return other->GetStackCount();
if (other->~IsInfiniteStackCount())
{
SetInfiniteStackCount();
return other->GetStackCount();
}
var howmany = Min(other->GetStackCount(), MaxStackCount() - GetStackCount());
var future_count = GetStackCount() + howmany;
if (howmany <= 0 || future_count > Stackable_Max_Count)
return 0;
SetStackCount(future_count);
return howmany;
}
/**
* Defines how many objects are contained in this stack.
* If the stack count is set to a value less than 1
* the object gets removed.
* If the stack was infinite, then it will be finite
* after setting the stack count.
* @par amount this many objects are in the stack.
* @return always returns true.
*/
public func SetStackCount(int amount)
{
count = BoundBy(amount, 0, Stackable_Max_Count); // allow 0, so that the object can be removed in UpdateStackDisplay
count_is_infinite = false;
UpdateStackDisplay();
return true;
}
/**
* Changes the stack count.
* If the stack count is set to a value less than 1
* the object gets removed.
* Does not affect infinite stacks.
* @par change the stack count will be changed by this
amount.
*/
public func DoStackCount(int change)
{
if (!(this->IsInfiniteStackCount()))
{
count += change;
UpdateStackDisplay();
}
}
/**
* Defines the stack as infinite:
* - one can always TakeObject() from the stack
* - always accepts stacking other stacks
* - if put into a container the first contained
* stack becomes infinite, this object gets removed
*/
public func SetInfiniteStackCount()
{
count = Stackable_Infinite_Count;
count_is_infinite = true;
UpdateStackDisplay();
return true;
}
/**
* Takes one object from the stack, the
* stack count is reduced by 1.
* @return the object that was taken.
* This object is not contained.
*/
public func TakeObject()
{
if (GetStackCount() == 1)
{
Exit();
return this;
}
else
{
DoStackCount(-1);
var take = CreateObject(GetID(), 0, 0, GetOwner());
take->SetStackCount(1);
return take;
}
}
/**
* Updates the stack, concerning information
* that is required by other objects and the GUI.
* The stack is removed if the stack count is <= 0.
*/
public func UpdateStackDisplay()
{
// empty stacks have to be removed
if (GetStackCount() <= 0)
{
RemoveObject();
return;
}
// otherwise update the object
UpdatePicture();
UpdateMass();
UpdateName();
NotifyContainer();
}
/**
* Updates the picture. Called by UpdateStack().
*/
private func UpdatePicture()
{
// Allow other objects to adjust their picture.
return _inherited(...);
}
/**
* Updates the name. Called by UpdateStack().
* By default the name is changed to e.g. "5x Name"
* if the stack count is 5, or "Infinite Name" if
* the stack is infinite.
*/
private func UpdateName()
{
if (this->IsInfiniteStackCount())
SetName(Format("$Infinite$ %s", GetID()->GetName()));
else
SetName(Format("%dx %s", GetStackCount(), GetID()->GetName()));
}
/**
* Updates the mass. Called by UpdateStack().
* The mass is proportional to the mass of the definition.
* It is assumed that the mass of the definition is that of
* InitialStackCount() objects in one stack.
*/
private func UpdateMass()
{
SetMass(GetID()->GetMass() * Max(GetStackCount(), 1) / InitialStackCount());
}
/**
* Tells a possible container that the stack was
* changed.
* Calls NotifyHUD() in containers with extra slots,
* or OnInventoryChange() otherwise.
*/
private func NotifyContainer()
{
// notify hud
var container = Contained();
if (container)
{
// has an extra slot
if (container->~HasExtraSlot())
{
container->~NotifyHUD();
}
// is a clonk with new inventory system
else
{
container->~OnInventoryChange();
}
}
}
/**
* Tries merging packs BEFORE entering the container.
* That means that a container can not prevent objects stacking into it.
* However, the other way round (after the object has entered) presents more issues.
*/
protected func RejectEntrance(object into)
{
// Merge the stack into existing stacks.
if (MergeWithStacksIn(into)) return true;
// Finally, allow the object to reject the stack, if it filled existing stacks but still should
// not be allowed to enter. This is the case in the barrel: It contains a stack, fills that to
// the maximum and then prevents the remaining liquid from forming a second stack in the barrel
if (this && into->~RejectStack(this)) return true;
return _inherited(into, ...);
}
/**
* Value calculation. The value is proportional to the value of the definition.
* It is assumed that the definition value defines the value for InitialStackCount()
* objects in one stack.
*/
public func CalcValue(object in_base, int for_plr)
{
return GetID()->GetValue() * Max(GetStackCount(), 1) / InitialStackCount();
}
/**
* Tries to add this object to another stack.
* This call removes this item if its stack
* count is reduced to 0, or if it is an
* infinite stack.
* @par other the other stack.
* @return true if successful. That is, if
* one or more objects were transferred
* to the other stack.
*/
public func TryAddToStack(object other)
{
if (other == this) return false;
// Is a stack possible in theory?
if (other->~IsStackable() && other->GetID() == GetID())
{
var howmany = other->Stack(this);
if (howmany > 0)
{
var stack = this;
DoStackCount(-howmany);
if (stack && stack->IsInfiniteStackCount()) stack->RemoveObject();
// Stack succesful! No matter how many items were transfered.
return true;
}
}
return false;
}
/**
* Attempts to add this stack to existing stacks in an object.
* By default the function considers stacks that are contained
* directly in the object 'into', as well as stacks that are
* contained in contents with HasExtraSlot() in 'into'.
*
* @par into stacks in this object are considered.
* @par ignore_extra_slot_containers if set to 'true' the stacks in
* contents with HasExtraSlot() will not be considered.
* The default value is 'false'.
* @par continue_on_limit_reached containers may have a stack size limit.
* RejectEntrance() should return 'true' to prevent
*/
public func MergeWithStacksIn(object into, bool ignore_extra_slot_containers)
{
// The object may grab contents first. The implementation of CollectFromStack()
// should ensure that no loop is created, however.
// This is used in the barrel from example: If the liquid stack that enters is too large,
// then the barrel grabs a single liquid item
into->~CollectFromStack(this);
ignore_extra_slot_containers = ignore_extra_slot_containers ?? false;
var contents = FindObjects(Find_Container(into));
if (!ignore_extra_slot_containers)
{
// first check if stackable can be put into object with extra slot
for (var container in contents)
{
if (!container)
continue;
if (container->~HasExtraSlot())
if (MergeWithStacksIn(container))
return true;
}
}
// then check this object
for (var stack in contents)
{
if (!stack)
continue;
TryAddToStack(stack);
if (!this) return true;
}
return false; // TODO was: added_to_stack
}
/**
* Relevant for the GUI only. Infinite stacks can be stacked on top of other infinite
* stacks only.
* This does not affect the functions Stack() or TryAddToStack().
*/
public func CanBeStackedWith(object other)
{
// Infinite stacks can only be stacked on top of others.
if (this->IsInfiniteStackCount() != other->~IsInfiniteStackCount())
return false;
// If this and other are contained in extra slots stack count must be the same for the parents to be stackable.
if (this->Contained() && other->Contained())
if (this->Contained()->~HasExtraSlot() && other->Contained()->~HasExtraSlot())
return this->GetStackCount() == other->~GetStackCount() && _inherited(other, ...);
return _inherited(other, ...);
}
/**
* Infinite stacks show a little symbol in their corner.
*/
public func GetInventoryIconOverlay()
{
if (!(this->IsInfiniteStackCount())) return nil;
return {Left = "50%", Bottom = "50%", Symbol = Icon_Number, GraphicsName = "Inf"};
}
/**
* Saves stack counts in saved scenarios.
*/
public func SaveScenarioObject(props)
{
if (!inherited(props, ...)) return false;
props->Remove("Name");
if (this->IsInfiniteStackCount())
props->AddCall("Stack", this, "SetInfiniteStackCount");
else if (GetStackCount() != InitialStackCount())
props->AddCall("Stack", this, "SetStackCount", GetStackCount());
return true;
}
/**
* Offers editor properties for stack count and inifnite setting
*/
public func Definition(def, ...)
{
_inherited(def, ...);
if (!def.EditorProps) def.EditorProps = {};
def.EditorProps.count = { Name="$Count$", Type="enum", AsyncGet="GetEditorStackCount", Set="SetEditorStackCount", Options=[
{ Name=Format("$DefaultStack$", def->InitialStackCount()), Value=def->InitialStackCount() },
{ Name="$CustomStack$", Type=C4V_Int, Value=def->InitialStackCount(), Delegate={ Type="int", Min=1/*, Max=def->MaxStackCount()*/ } }, // there's no reason to restrict the max stack in editor
{ Name="$Infinite$", Value="infinite" }
]};
}
private func GetEditorStackCount()
{
if (count_is_infinite) return "infinite"; else return count;
}
private func SetEditorStackCount(to_val)
{
if (to_val == "infinite") SetInfiniteStackCount(); else SetStackCount(to_val);
}
|