File: Script.c

package info (click to toggle)
openclonk 8.1-4
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 169,520 kB
  • sloc: cpp: 180,479; ansic: 108,988; xml: 31,371; python: 1,223; php: 767; makefile: 145; sh: 101; javascript: 34
file content (439 lines) | stat: -rw-r--r-- 12,742 bytes parent folder | download | duplicates (5)
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);
}