#charset "us-ascii"
#include "advlite.h"
/*
* EventListItem Extension
*. Version 1.1 17-Dec-2022
*. By Eric Eve based on work by John Ziegler
*/
/*
* An EventListItem is an object that can be used within an EventList but is only used when
* certain conditions are met (its isReady property evaluates to true, the game clock time is at
* or after any next firing time we have defined, and it hasn't already been used for any maximum
* number of times we care to define).
*
* EventListItems can be added to a regular EventList object by locating them within that object
* using the + notation. [EVENTLISTITEM EXTENSION]
*
*/
class EventListItem: PreinitObject
/*
* Add this EventListItem to the eventList of the EventList object with which is is to be
* associated.
*/
execute()
{
/*
* if myListObj is defined, add us to that EventList object. whichList is by default
* &eventList, but this could be changed to &firstEvents or some other custom property
*/
if(myListObj)
myListObj.(whichList) += self;
/*
* if we don't specify myListObj but we have a location, add us to our
* location.(whichList)
*/
else if(location &&
location.propDefined(whichList) &&
location.(whichList)!=nil &&
location.(whichList).ofKind(Collection))
{
location.(whichList) += self;
myListObj = location;
}
}
/*
* we usually want to add objects to a ShuffledEventList's eventList property, but
* items/subclasses could change this to be added to firstEvents or some other alternate list
* within the EventList object
*/
whichList = &eventList
/*
* The EventList object to which we are to belong. If this is left at nil, our location will
* be used.
*/
myListObj = nil
/*
* When the event list to which we've been added gets to us, it will call our doScript()
* method, so we use that to define what happens.
*/
doScript()
{
/* If we're in a position to fire, then carry out our invocation. */
if(canFire())
{
_invokeItem();
/* If we've been mixed in with an EventList class, call the inherited handling. */
if(ofKind(Script))
inherited();
}
/* Otherwise, use our fallback routine. */
else
fallBack();
}
_invokeItem()
{
invokeItem();
//keep track of how many times this item has shown
++fireCt;
//keep track of when we fired last
lastClock = gTurns;
// automatically remove if we have exceeded our maxFireCt or met our doneWhen condition
if((maxFireCt && fireCt >= maxFireCt) || doneWhen)
setDone();
/* Delay our next use until at least minInterval turns have elapsed. */
setDelay(minInterval);
/* Reset our missed turn flag to nil as we haven't missed this turn. */
missedTurn = nil;
}
/*
* Here goes the code (or a double-quoted string) that carries out what we do when we're
* invoked. Game code will need to define what should happen here.
*/
invokeItem()
{
}
/*
* This first turn on which we came up in our EventList but were unable to fire, or nil if we
* have either not missed or turn or fired on the previous occasion we could.
*/
missedTurn = nil
/*
* The method that defines what this EventListItem does if it's invoked when it's not ready to
* fire.
*/
fallBack()
{
/*
* If possible, get our myListObj to use the next item in its list, so that it behaves as
* if we werem't here. However, we need to make sure it's safe to do that without getting
* into an infinite loop, so to be on the safe side we check (1) that there's at least one
* item in the list which our myListObj could invoke (i.e. something that's not an
* EventListItem that can't fire) and (2) that myListOnj is not a StopEventList that's
* reached its end, which might then repeatedly try to invoke us.
*/
if(myListObj.(whichList).indexWhich({x: !(x.ofKind(EventListItem) && !x.canFire())})
&& !(myListObj.ofKind(StopEventList)&& myListObj.curScriptState >= eventListLen))
myListObj.doScript();
/* Otherwise, use our fallBackResponse */
else
fallBackResponse();
/*
* Unless we're done or we've already noted a missed turn, note that we missed our chance
* to fire with our own response this turn.
*/
if(!isDone && missedTurn == nil)
missedTurn = gTurns;
}
/*
* The response to use if all else fails, that is if there we cannot fire ourselves and there
* is no non-EventListItem (which could be used in our place) in the eventList to which we
* belong. This could, for exmple, display another message or it could just do nothing, which
* is the default. We only need to supply something here if we belong to an EventList that
* should display something every turn, for example as a response to a DefaultTopic or else if
* we are or may be the final item in a StopEventList.
*/
fallBackResponse() { }
/*
* Is this EventListItem ready to fire? Note that this is addition to its not being done and
* having reached its ready time.
*/
isReady = true
/*
* Can this EventListItem item fire? By default it can if its isReady condition is true and it
* is not already done (isDone != true) and the turn count exceeds its ready time.
*/
canFire()
{
return isReady && !isDone && gTurns >= readyTime;
}
/* Have we finished with this EventListItem? */
isDone = nil
/* Set this EventListItem as having been done */
setDone()
{
isDone = true;
}
/* The number of times this EventListItem has fired. */
fireCt = 0
/*
* Flag: can this EventListItem be removed from its eventList once isDone = true? By default
* it can, but note that this flag only has any effect when our EventList's resetEachCycle
* property is true. We might want to set this to nil if isDone might become nil again on this
* EventListItem, to avoid it being cleared out of its eventList.
*/
canRemoveWhenDone = true
/*
* The maximum number of times we want this EventListItem to fire. The default value of nil
* means that this EventListItem can fire an unlimited unmber of times. For an EventListItem
* that fires only once, set maxFireCt to 1 or use the ELI1 subclass.
*/
maxFireCt = nil
/*
* An alternative condition (which could be defined as a method) which, if true, causes this
* EventListItem to be finished with (set to isDone = true). Note that isDone will be set to
* try either if this EventListItem exceeds its maaFireCt or if its doneWhen method/property
* evaluates to true.
*/
doneWhen = nil
/* The last turn on which this EventListItem fired */
lastClock = 0
/*
* The turn count that must be reached before we're ready to fire. By default this is 0, but
* game code can use this or set the setDelay() method to set/reset it.
*/
readyTime = 0
/* The minimum interval (in number of turns) between repeated occurrences of this item. */
minInterval = 0
/*
* Set the number of turns until this EventListItem can be used again. This could, for
* example, be called from invokeItem() to set a minimum interval before this EventListItem is
* repeated.
*/
setDelay(turns)
{
readyTime = gTurns + turns;
return self;
}
/* Get the actor with which we're associated if we have one. */
getActor
{
local obj = [location, myListObj].valWhich({x:x && (x.ofKind(ActorState) ||
x.ofKind(Actor) || x.ofKind(AgendaItem)) });
if(obj)
return obj.getActor;
else
return nil;
}
/*
* Has this EventListItem been underused? By default it has if it hasn't been used at all or
* it missed out the last time it was called by not being ready, but game code can override if
* it wants to employ some other condition, such as the number of times we've been used in
* relation to other items in our listObj. The purpose of this is to allow RandomFiringScripts
* to prioritize underused EventListItems once they become ready to fire.
*/
underused()
{
/*
* By default we're underused if we've we've missed a turn on which we would have fired
* had we been ready to, but game code can override this to some other condition if
* desired, such as testing whether fireCt == 0
*/
return (missedTurn != nil);
}
/*
* Add this EventListItem to the whichList list of myListObj_. If specificied, whichList must
* be supplied as a property, and otherwise defaults to &eventList. A minimium interval
* between firings of this EventList item can optionally be specified in the minInterval_
* parameter, but there is no need to do this if this EventList already defines its own
* minInterval or doesn't require one.
*/
addToList(myListObj_, whichList_ = &eventList, minInterval_?)
{
/* Store our parameters in the appropriate properties. */
myListObj = myListObj_;
whichList = whichList_;
if(minInterval_)
minInterval = minInterval_;
/* Get our list object to add us to its appropriate list property. */
myListObj.addItem(self, whichList);
}
;
/*
* Short form EventListItem class names for the convenience of game authors who want to save
* typing.
*/
class ELI: EventListItem;
/* A one-off EventListItem */
class ELI1: EventListItem
maxFireCt = 1
;
modify EventList
/*
* Game code can call this method to remove all EventListItems that have been finished with
* (isDone = true) from the eventList of this EventList. This probably isn't necessary unless
* there are likely to be a large number of such items slowing down execution.
*/
resetList()
{
/*
* Reduce our eventList to exclude items that are EventListItems for which isDone is true
* and the canRemoveWhenDone flag is true.
*/
self.eventList = self.eventList.subset({x: !(objOfKind(x, EventListItem) && x.isDone &&
x.canRemoveWhenDone)});
/* Recache our eventList's new length. */
eventListLen = eventList.length();
}
/*
* Flag, do we want to reset the list each time we've run through all our items? By default we
* don't, but this might ba en appropriate place to call resetList() if we do want to call it.
* Note that this is in any case irrelevant on the base EventList class but may be relevant on
* some of its subclaases (CyclicEventList, RandomEventList and ShuffledEventList).
*/
resetEachCycle = nil
/*
* Add an item to prop (usually eventList) property of this EventList, where prop should be
* supplied as a property pointer,
*/
addItem(item, prop)
{
/* Add the item to the specified list. */
self.(prop) += item;
/* Chache our new eventList length. */
eventListLen = eventList.length;
}
;
modify CyclicEventList
advanceState()
{
/*
* If we want to reset our eventList each cycle to clear out any spent EventListItems and
* our current script state has reache our eventList's length (so that we're at the end of
* a cycle), then call our resetList() method.
*/
if(resetEachCycle && curScriptState >= eventListLen)
resetList();
/* Carry out the inherited handling */
inherited();
}
;
/* Mofiications to ShuffledEventList for EventListItem extension */
modify ShuffledEventList
/*
* For the EventListItem extenstion we modify this method so that it first chooses any as yet
* unused EventListItem from our eventList that's now ready to fire. If none is found, we use
* the inherited behaviour to select the next item indicated by our shuffledList_ .
*/
getNextRandom()
{
/*
* If we want to clear up isDone items and we have a shuffledList_ and that list has no
* more values available, then reset our list to remove the isDone items.
*/
if(resetEachCycle && shuffledList_ && shuffledList_.valuesAvail == 0)
resetList();
/*
* If we have an underused EventListItem that's ready to fire, choose that.
*/
local idx = underusedReadyELIidx();
/*
* If we found a suitable value and idx is not nil, return idx. Otherwise use the
* inherited value
*/
return idx ?? inherited();
}
/*
* Reset our eventList to clear out EventListItems that are done with (isDone = true). This is
* not called from any library code by default, but can be called from game code if game
* authors are worried about an accumulation of too many spent EventListItems in any given
* eventList. For many games, this probably won't be necessary.
*
* One potentially good place to call this from as at the end of each iteration of a
* ShuffledEventList, when the items are about to be reshuffled in any case. You can make this
* happen by setting the resetOnReshuffle property to true,
*/
resetList()
{
/* Carry out the inherited handling */
inherited();
/*
* recreate our shuffled integer list, since the existing one may index items that no
* lomger exist in our eventList.
*/
shuffledList_ = new ShuffledIntegerList(1, eventListLen);
/* apply our suppressRepeats option to the shuffled list */
shuffledList_.suppressRepeats = suppressRepeats;
}
addItem(item, prop)
{
/* Carry out the inherited handling */
inherited(item, prop);
/* Reset our list to include the item we've just added and clear out any spent ones. */
resetList();
}
;
modify RandomEventList
/*
* Get the next random state. By default, we simply return a number from 1 to the number of
* entries in our event list. This is a separate method to allow subclasses to customize the
* way the random number is selected. However, if we have an unused EventListItem that's ready
* to fire, we select that instead, to make sure it gets a look-in at the earliest possible
* opportunity.
*/
getNextRandom()
{
/*
* For a RandomEventList we regard a 'cycle' as being the firing of the number of items in
* the eventList (regardless of whether each individual item has been fired). So we
* increment our fireCt each time we're called, and then reset it to zero once it reaches
* our eventLiatLen. Then, if thie RandeomEventList wants to resetEachCycle, we clear out
* any spent EventListItems.
*/
if(++fireCt >= eventListLen)
{
/* Reset our fireCt to zero */
fireCt = 0;
/*
* Call resetList() to clear out any spent EventListItems if we want to reset each
* cycle.
*/
if(resetEachCycle)
resetList();
}
/*
* If we have an underused EventListItem that's ready to fire, choose that
*/
local idx = underusedReadyELIidx();
/*
* If we found a suitable value and idx is not nil, return idx. Otherwise use the
* inherited value
*/
return idx ?? inherited();
}
/* The number of times we have fired on this 'cycle '*/
fireCt = 0
;
modify RandomFiringScript
/*
* Return the index within our eventList of any as yet unused EventListItem that's ready to
* fire. This is principally for the use of our RandomEventList and ShuffledEventList
* subclasses.
*/
underusedReadyELIidx()
{
/* Extract a subset list of EventListItems that can fire and are underused. */
local lst = eventList.subset({x: objOfKind(x, EventListItem) && x.canFire()
&& x.missedTurn && x.underused()});
/* If the list is empty, we have no underused EventListItem ready to fire, so return nil. */
if(lst.length < 1)
return nil;
/* Sort the list in ascendcing order of their missedTurns. */
lst = lst.sort({a, b: a.missedTurn - b.missedTurn});
/*
* Return the index of the first element in the list, which will be the one that missed
* its turn longest ago.
*/
return eventList.indexOf(lst[1]);
}
;
Adv3Lite Library Reference Manual
Generated on 03/07/2024 from adv3Lite version 2.1