forked from EricEve/adv3lite
-
Notifications
You must be signed in to change notification settings - Fork 0
/
gadget.t
447 lines (367 loc) · 13.8 KB
/
gadget.t
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
440
441
442
443
444
445
446
#charset "us-ascii"
#include "advlite.h"
/*
* ***************************************************************************
* gadget.t
*
* This module forms part of the adv3Lite library (c) 2012-13 Eric Eve
*
*
*
* This module contains definitions for various control gadgets like buttons,
* levers and dials.
*/
/* A Button is an object that does something when pressed */
class Button: Thing
/* a button is usually fixed to something */
isFixed = true
/* Handle the Push command */
dobjFor(Push)
{
/* A Button is a good choice for a PUSH command */
verify() { logicalRank(120); }
/* Execute our makePushed method when we're pushed */
action() { makePushed(); }
/* If nothing else happens, just say 'Click!' */
report() { DMsg(click, 'Click!'); }
}
/*
* Carry out the effects of pushing the button here. Particular Button
* objects will need to override this method to carry out the effect of
* pushing the button.
*/
makePushed() { }
;
/*
* A Lever is an object that can be in one of two positions: pulled (isPulled
* = true) or pushed (isPulled = nil), and which can be pulled and pushed
* between those two positions.
*/
class Lever: Thing
/* a lever is usually fixed to something */
isFixed = true
/* is this lever in the pulled or pushed position. */
isPulled = nil
/*
* By default we make isPushed the opposite of isPulled, but we defined
* them as separate properties in case we want a lever that can be in more
* than two positions, and so might be in an intermediate position that is
* neither pushed nor pulled.
*/
isPushed = (!isPulled)
/*
* Carry out pushing or pulling the lever. Note that this would need to be
* overridden on a Lever that can be in more than two states.
*/
makePulled(stat)
{
/* Set isPulled to stat */
isPulled = stat;
}
/* Handle Pulling this Lever */
dobjFor(Pull)
{
verify()
{
/*
* A Lever can't be pulled any further if it's already in the
* pulled position
*/
if(isPulled)
illogicalNow(alreadyPulledMsg);
}
/* Use the makePulled() method to handle pulling the lever */
action() { makePulled(true); }
/* The default report to display after pulling one or more levers */
report() { DMsg(okay pulled, 'Done|{I} pull{s/ed} {1}', gActionListStr); }
}
/* The message to display when we can't be pulled any further */
alreadyPulledMsg = BMsg(already pulled, '{The subj dobj} {is} already in the
pulled position. ')
/* Handle Pushing this Lever */
dobjFor(Push)
{
verify()
{
/*
* A Lever can't be pushed any further if it's already in the
* pushed position
*/
if(isPushed)
illogicalNow(alreadyPushedMsg);
}
/* Use the makePulled() method to handle pushing the lever */
action() { makePulled(nil); }
/* The default report to display after pushing one or more levers */
report() { DMsg(okay pushed, 'Done|{I} push{es/ed} {1}', gActionListStr); }
}
/* The message to display when we can't be pushed any further */
alreadyPushedMsg = BMsg(already pushed, '{The subj dobj} {is} already in the
pushed position. ');
;
/*
* A Settable is anything that can be set to particular values, such as a
* slider control or a dial. Various types of dial descending from Settable
* are defined below.
*/
class Settable: Thing
/*
* a list of the valid settings this Settable can have, given as list of
* single-quoted strings.
*/
validSettings = []
/* our current setting */
curSetting = nil
/*
* Put the setting into a standard form so it can be checked for validity.
* By default we turn it into lower case so that it doesn't matter what
* case the player types the desired setting in. We also strip any
* enclosing quotes that might have been used to pass an awkward value
* like "1.4" that the parser would otherwise misinterpret.
*/
canonicalizeSetting(val)
{
return stripQuotesFrom(val.toLower());
}
/* Set this Settable to its new setting, val */
makeSetting(val)
{
/* Update our current setting to the canonicalized version of val */
curSetting = canonicalizeSetting(val);
}
/*
* Check whether the proposed setting is valid. By default it is if the
* canonicalized version of val is present in our list of valid settings.
*/
isValidSetting(val)
{
/* Convert val into its canonicalized equivalent. */
val = canonicalizeSetting(val);
/*
* Determine whether val is present in our list of valid settings and
* return true or nil accordingly
*/
return validSettings.indexOf(val) != nil;
}
/* A Settable is something that can be set to various values */
canSetMeTo = true
/* Handle a SET TO command targeted at this Settable */
dobjFor(SetTo)
{
/* Check whether we're being set to a valid setting */
check()
{
/*
* If the player is trying to set us to our current setting,
* display a message to that effect (which will halt the action).
*/
if(curSetting == canonicalizeSetting(gLiteral))
say(alreadySetMsg);
/*
* If the player is trying to set us to an invalid setting,
* display a message to that effect (which will halt the action).
*/
if(!isValidSetting(gLiteral))
say(invalidSettingMsg);
}
/* Note that the action() handling for SET TO is defined on Thing */
}
invalidSettingMsg = BMsg(invalid setting, 'That {dummy} {is} not a valid
setting for {the dobj}. ')
okaySetMsg = BMsg(okay set, '{I} {set} {the dobj} to {1}. ', curSetting)
alreadySetMsg = BMsg(already set, '{The subj dobj} {is} already set to {1}.
', curSetting)
/*
* Most gadgets of this sort are part of or attached to something else, so
* we make them fixed in place by default
*/
isFixed = true
;
/* A Dial is Simply a Settable we can turn as well as set */
class Dial: Settable
dobjFor(TurnTo) asDobjFor(SetTo)
canTurnMeTo = true
;
/*
* A Numbered Dial is a Dial that can be turned to any integer in a defined
* range of numbers.
*/
class NumberedDial: Dial
/* The lowest number to which this dial can be turned. */
minSetting = 0
/* The highest number to which this dial can be turned. */
maxSetting = 100
/*
* If the spelledToInt() function is defined then allow the dial to be
* turned to a spelt-out number as well as a number given in digits, e.g.
* TURN DIAL TO FORTY-THREE as well as TURN DIAL TO 43.
*/
canonicalizeSetting(val)
{
/* Get the inherited value */
val = inherited(val);
/* Try to convert it to a number */
local num = defined(spelledToInt) ? spelledToInt(val) : nil;
/*
* If the conversion was successful, convert val to a string
* representation of the number (e.g. '43').
*/
if(num)
val = toString(num);
/* Return val, changed as need be. */
return val;
}
/* Is val a valid setting for this dial? */
isValidSetting(val)
{
/* Convert val into its canonicalized equivalent. */
val = canonicalizeSetting(val);
/*
* Try converting val to either a real number or an integer depending
* on whether allowDecimal is true or nil.
*/
val = allowDecimal ? tryNum(val) : tryInt(val);
/*
* Val is valid if it lies between our minimum and maximum settings
* (inclusively)
*/
return val != nil && (val >= minSetting && val <= maxSetting);
}
allowDecimal = nil
;
/*
* Mix-in class to help with inventory management. A BagOfHolding can be mixed
* in with a Container (or, less usually, Surface, RearContainer or Underside)
* to provide an object which, if held by the player character, will be used
* to move objects in the player character's inventory to if his/her hands
* become too full to pick up another object.
*/
class BagOfHolding: object
/*
* The affinity for this BagOfHolding for obj. This can be used to
* determined how 'willing' a particular BagOfHolding is to contain obj. A
* value of less than 1 means that the BagOfHolding can't contain obj at
* all. The higher the affinity, the better the choice this BagOfHolding
* is for obj. The default value is 100, or 0 for a BagOfHolding's
* affinity for itself.
*/
affinityFor(obj)
{
return obj == self ? 0 : 100;
}
/*
* To be suitable to contain obj a BagOfHolding must have enough spare
* capacity for it. If it has, its suitability is its affinity for obj;
* otherwise it's 0. A BagOfHolding is also unsuitable if it's locked.
*/
suitabilityFor(obj)
{
if(obj.bulk > bulkCapacity - getBulkWithin || obj.bulk > maxSingleBulk
|| isLocked || obj.isFixed)
return 0;
return affinityFor(obj);
}
/*
* Class method to determine whether the actor is carrying a suitable
* BagOfHolding that could be used to move something from his inventory
* into, and then to move items from the actor's inventory into an
* appropriate bag of holding.
*/
tryHolding(obj)
{
/* Obtain a Vector containing the BagsOfHolding carried by the actor. */
local bohVec = gActor.contents.subset({x: x.ofKind(BagOfHolding)});
/*
* If the actor is not carrying a BagOfHolding, there's nothing more
* we can do, so just stop here.
*/
if(bohVec.length == 0)
return;
/* The amount of bulk we need to free up */
local bulkToFree = gActor.getCarriedBulk + obj.bulk -
gActor.bulkCapacity;
local idx = 1;
local carriedList = gActor.contents.subset({o: o.wornBy == nil
&& o.isFixed == nil});
while(bulkToFree > 0 && carriedList.length >= idx)
{
local objToMove = carriedList[idx];
/*
* If we have more than one BagOfHolding available, sort our
* vector of BagsOfHolding in descending order of suitability
*/
if(bohVec.length > 1)
bohVec.sort(SortDesc, {a, b: a.suitabilityFor(objToMove) -
b.suitabilityFor(objToMove) });
/*
* Choose the first one in the Vector, which will be the most
* suitable.
*/
local bagToUse = bohVec[1];
/*
* If the most suitable bag of holding for the object we're trying
* to move isn't suitable, try again with another object.
*/
if(bagToUse.suitabilityFor(objToMove) < 1)
{
idx++;
continue;
}
/*
* Get the action needed to move an object into the selected
* BagOfHolding.
*/
local action = bagToUse.moveAction();
/*
* If the action is nil, then there's something wrong with the
* selected BagOfHolding. Break of of the loop so we don't get
* stuck in an infinite loop.
*/
if(action == nil)
break;
/*
* Try moving the selected object into the selected bag using the
* appropriate action depending on the bag's contType
*/
tryImplicitAction(action, objToMove, bagToUse);
/*
* Reset the index into the contents list to 1 so that if we need
* to select another object we start from the beginning again.
*/
idx = 1;
/* Recalculate the amount of bulk left to free */
bulkToFree = gActor.getCarriedBulk + obj.bulk -
gActor.bulkCapacity;
/*
* Remove objToMove from the carried list. (Even if it wasn't
* actually moved for any reason, we don't want to try moving it
* again).
*/
carriedList -= objToMove;
}
}
/* The action needed to move an object into me. */
moveAction()
{
switch(contType)
{
case In:
return PutIn;
case On:
return PutOn;
case Under:
return PutUnder;
case Behind:
return PutBehind;
}
if(remapIn)
return PutIn;
if(remapOn)
return PutOn;
if(remapUnder)
return PutUnder;
if(remapBehind)
return PutBehind;
return nil;
}
;