Everyone’s reaction to Mike speedrunning Cragne.
>INVENTORY
You have 3 bottles of sports drink and 5 amphetamine tablets.
Ha! There’s definitely a lot of black tea that’ll go into my planning (black like the heart of Vaadignephod!) but that’s about as crazy as I get.
Okay, so I entered a coding trance and made a thing.
It hasn’t been bug-tested too thoroughly, but it’s sort of an experimental proof-of-concept. (This isn’t to say there was no bug-testing! I did about 2 passes, but I’m certain I haven’t found all the possibilities.)
(DISCLAIMER: This is made for Adv3Lite, and has not been tested in Adv3 yet!! All Adv3 authors should proceed with caution!)
(WARNING: allHeur.t
is pretty long! It contains a lot of default configurations!)
allHeur.t
// Just some adjustments to the behavior of "X ALL" actions
enum notifDangerous, notifImportant, notifUnexamined;
class AllHeurNotification: object {
construct(_obj, _reason) {
obj = _obj;
reason = _reason;
}
obj = nil
reason = nil
}
allHeurCore: InitObject {
notifierVec = static new Vector()
infoDaemon = nil
enforceExamining = true
doExamineAfterTake = nil
enforceCarefulTake = true
enforceCarefulDrop = true
execute() {
infoDaemon = new PromptDaemon(self, &afterTurn);
}
afterTurn() {
if (notifierVec.length > 0) {
local dangerousLst = [];
local importantLst = [];
local unexaminedLst = [];
for (local i = 1; i <= notifierVec.length; i++) {
local notif = notifierVec[i];
switch (notif.reason) {
case notifDangerous:
dangerousLst += notif.obj;
break;
case notifImportant:
importantLst += notif.obj;
break;
case notifUnexamined:
unexaminedLst += notif.obj;
break;
}
}
if (dangerousLst.length + importantLst.length + unexaminedLst.length > 0) {
local strBfr = new StringBuffer(20);
strBfr.append('\b');
if (dangerousLst.length > 0) {
appendReasonList(dangerousLst, strBfr, 'create{s/d} handling risks');
}
if (importantLst.length > 0) {
appendReasonList(importantLst, strBfr, '{is} important to keep in inventory');
}
if (unexaminedLst.length > 0) {
appendReasonList(unexaminedLst, strBfr, '{have} not been examined');
}
say(toString(strBfr));
}
notifierVec.removeRange(1, -1);
}
}
appendReasonList(lst, strBfr, reasonStr) {
strBfr.append('\n(\^');
strBfr.append(makeListStr(lst, &theName, 'and'));
strBfr.append('{prev} ');
strBfr.append(reasonStr);
strBfr.append(', and {do} not qualify for "ALL".)\n');
}
checkNotifiedFor(obj, reason) {
for (local i = 1; i <= notifierVec.length; i++) {
if (notifierVec[i].obj == obj) {
return true;
}
}
notifierVec.append(new AllHeurNotification(obj, reason));
return nil;
}
}
modify Thing {
isDangerousInventory = nil
isImportantInventory = nil
isFamiliarInventory = nil
hideFromAll(action) {
if (action.skipsCheckAllSafeties) { // Any qualifying actions are guaranteed safe
return hideFromAllNormally(action);
}
if (!action.skipsCheckCarefulTake) {
if (isDangerousInventory) { // Do not accidentally interact with dangerous things
if (allHeurCore.enforceCarefulTake) noteDangerous();
return allHeurCore.enforceCarefulTake;
}
}
if (!action.skipsCheckExamining) {
if (!(examined || isFamiliarInventory)) { // Do not accidentally interact with unexamined things
if (allHeurCore.doExamineAfterTake) return nil;
if (allHeurCore.enforceExamining) noteUnexamined();
return allHeurCore.enforceExamining;
}
}
if (!action.skipsCheckCarefulDrop) { // Only dump unimportant things
if (isImportantInventory) {
if (allHeurCore.enforceCarefulDrop) noteImportant();
return allHeurCore.enforceCarefulDrop;
}
}
return hideFromAllNormally(action);
}
hideFromAllNormally(action) {
return nil;
}
noteDangerous() {
allHeurCore.checkNotifiedFor(self, notifDangerous);
}
noteImportant() {
allHeurCore.checkNotifiedFor(self, notifImportant);
}
noteUnexamined() {
allHeurCore.checkNotifiedFor(self, notifUnexamined);
}
dobjFor(Take) {
action() {
inherited();
if (gActor == gPlayerChar) {
isFamiliarInventory = true;
if (allHeurCore.doExamineAfterTake && !examined) {
extraReport('({I} {take} {the dobj} and examine{s/d} {him dobj}.)\b');
doNested(Examine, self);
}
}
}
}
preinitThing() {
inherited();
doPlayerHoldingCheck();
}
moveInto(loc) {
inherited(loc);
doPlayerHoldingCheck();
}
doPlayerHoldingCheck() {
if (isHeldBy(gPlayerChar)) {
isFamiliarInventory = true;
}
}
}
modify Action {
skipsCheckAllSafeties = nil
skipsCheckCarefulDrop = (isFinnicky)
skipsCheckCarefulTake = nil // Never X ALL with something dangerous!!
skipsCheckExamining = (isFinnicky)
isFinnicky = nil
}
/*
* STUFF THAT GATHERS INFO SAFELY
*/
modify Examine {
skipsCheckAllSafeties = true
}
modify Inventory {
skipsCheckAllSafeties = true
}
modify ListenTo {
skipsCheckAllSafeties = true
}
modify Look {
skipsCheckAllSafeties = true
}
modify LookBehind {
skipsCheckAllSafeties = true
}
modify LookThrough {
skipsCheckAllSafeties = true
}
modify LookUnder {
skipsCheckAllSafeties = true
}
modify LookHere {
skipsCheckAllSafeties = true
}
modify Read {
skipsCheckAllSafeties = true
}
modify SmellSomething {
skipsCheckAllSafeties = true
}
modify TopicTAction {
skipsCheckAllSafeties = true
}
modify SystemAction {
skipsCheckAllSafeties = true
}
modify ImplicitConversationAction {
skipsCheckAllSafeties = true
}
modify MiscConvAction {
skipsCheckAllSafeties = true
}
modify TravelAction {
skipsCheckAllSafeties = true
}
/*
* STUFF THAT IS FINNICKY
* (Meaning it's harmless enough, and would be infuriating to have checks for)
*/
modify Board {
isFinnicky = true
}
modify Climb {
isFinnicky = true
}
modify ClimbDown {
isFinnicky = true
}
modify ClimbDownVague {
isFinnicky = true
}
modify ClimbUp {
isFinnicky = true
}
modify ClimbUpVague {
isFinnicky = true
}
modify Close {
isFinnicky = true
}
modify Enter {
isFinnicky = true
}
modify EnterOn {
isFinnicky = true
}
modify Extinguish {
isFinnicky = true
}
modify Flip {
isFinnicky = true
}
modify Follow {
isFinnicky = true
}
modify GetOff {
isFinnicky = true
}
modify GetOutOf {
isFinnicky = true
}
modify GoThrough {
isFinnicky = true
}
modify Jump {
isFinnicky = true
}
modify JumpOff {
isFinnicky = true
}
modify JumpOffIntransitive {
isFinnicky = true
}
modify JumpOver {
isFinnicky = true
}
modify Lie {
isFinnicky = true
}
modify LieIn {
isFinnicky = true
}
modify LieOn {
isFinnicky = true
}
modify Light {
isFinnicky = true
}
modify Lock {
isFinnicky = true
}
modify LockWith {
isFinnicky = true
}
modify Open {
isFinnicky = true
}
modify Pull {
isFinnicky = true
}
modify Push {
isFinnicky = true
}
modify PushTravelClimbDown {
isFinnicky = true
}
modify PushTravelClimbUp {
isFinnicky = true
}
modify PushTravelDir {
isFinnicky = true
}
modify PushTravelEnter {
isFinnicky = true
}
modify PushTravelGetOutOf {
isFinnicky = true
}
modify PushTravelThrough {
isFinnicky = true
}
modify Set {
isFinnicky = true
}
modify SetTo {
isFinnicky = true
}
modify Sit {
isFinnicky = true
}
modify SitIn {
isFinnicky = true
}
modify SitOn {
isFinnicky = true
}
modify Stand {
isFinnicky = true
}
modify StandIn {
isFinnicky = true
}
modify StandOn {
isFinnicky = true
}
modify SwitchOff {
isFinnicky = true
}
modify SwitchOn {
isFinnicky = true
}
modify SwitchVague {
isFinnicky = true
}
modify Turn {
isFinnicky = true
}
modify TurnTo {
isFinnicky = true
}
modify TurnWith {
isFinnicky = true
}
modify Type {
isFinnicky = true
}
modify TypeOn {
isFinnicky = true
}
modify TypeOnVague {
isFinnicky = true
}
modify Unlock {
isFinnicky = true
}
modify UnlockWith {
isFinnicky = true
}
modify Write {
isFinnicky = true
}
modify WriteOn {
isFinnicky = true
}
/*
* STUFF THAT REQUIRES TAKING
*/
modify Feel {
skipsCheckCarefulDrop = true
}
modify Remove {
skipsCheckCarefulDrop = true
}
modify Take {
skipsCheckCarefulDrop = true
}
modify TakeFrom {
skipsCheckCarefulDrop = true
}
modify Taste {
skipsCheckCarefulDrop = true
}
/*
* STUFF THAT REQUIRES DROPPING
*/
modify Doff {
skipsCheckExamining = true
}
modify Drop {
skipsCheckExamining = true
}
Documentation
Simply pop this into your project directory, and include it with:
#include "allHeur.t"
For every X ALL command, the appropriate Action
is polled for some “recommended safety checks”. The default properties for an Action
are the following:
skipsCheckAllSafeties = nil
skipsCheckCarefulDrop = (isFinnicky)
skipsCheckCarefulTake = nil // Never X ALL with something dangerous!!
skipsCheckExamining = (isFinnicky)
isFinnicky = nil
These check will interact with any Thing
that is considered for the X ALL command, according to some new properties. The defaults are the following:
isDangerousInventory = nil
isImportantInventory = nil
isFamiliarInventory = nil
“Vanilla” Adv3Lite Action
s have already been included in allHeur.t
, pre-configured.
Because this system makes heavy use of Thing.hideFromAll(action) { ... }
, it is recommended that you override the new Thing.hideFromAllNormally(action) { ... }
to tweak secondary behaviors.
You can modify some system-wide options for behavior, like so:
modify allHeurCore {
enforceExamining = true
enforceCarefulTake = true
enforceCarefulDrop = true
}
enforceExamining
Will the game force the player to examine a Thing
before it can be included in X ALL?
Skipped with any of the following properties on an Action
object:
actionNameHere.skipsCheckExamining = true
actionNameHere.isFinnicky = true
actionNameHere.skipsCheckAllSafeties = true
enforceCarefulTake
Will the game forbid X ALL any Thing
with the property isDangerousInventory = true
?
Skipped with any of the following properties on an Action
object:
actionNameHere.skipsCheckCarefulTake = true
actionNameHere.skipsCheckAllSafeties = true
enforceCarefulDrop
Will the game forbid X ALL on Thing
s with the property isImportantInventory = true
?
Skipped with any of the following properties on an Action
object:
actionNameHere.skipsCheckCarefulDrop = true
actionNameHere.isFinnicky = true
actionNameHere.skipsCheckAllSafeties = true
By default, for every Action
:
skipsCheckCarefulDrop = (isFinnicky)
skipsCheckExamining = (isFinnicky)
These properties can be overridden to nuance how an Action
interacts with this system.
skipsCheckAllSafeties = true
will make the Action
exclude itself from this system entirely.
skipsCheckExamining = true
can be added for any Action
that will not need to be examined before a TAKE ALL action behavior[1].
skipsCheckCarefulDrop = true
can be added for any Action
that will always be approved for a DROP ALL action behavior[1].
skipsCheckCarefulTake = nil
is recommended, as it prevents a potentially-dangerous action from being make with an X ALL command.
actionNameHere.isFinnicky = true
can be added to quickly make any Action
skip checks for TAKE ALL and DROP ALL action behaviors[1].
[1] I specify “action behavior” here, because this should be considered for actions that do not only make implicit or explicit TAKE/DROP behaviors, but would also logically require the player character to hold or let go of something, in order to execute the action.
Another option is available for the system’s core:
modify allHeurCore {
doExamineAfterTake = true
}
With doExamineAfterTake
enabled, any TAKE X or TAKE ALL command will automatically pass. Any taken Thing
s that have not yet been examined will also be automatically examined as part of the TAKE process.
This is to effectively enforce a “look after” behavior, instead of a “look before” behavior.
This also has a similar effect to:
modify allHeurCore {
enforceExamining = nil
}
REMEMBER: These are for X ALL commands specifically! Using this system will not enforce a player to EXAMINE CUP before being allowed to TAKE CUP, because individual items still behave like normal. In fact, individual items can be taken without examining them first, which will also qualify them to pass TAKE ALL checks later!
KNOWN STRANGE BEHAVIOR: When creating a new action that will be banned from X ALL contexts, you might need to have the following properties set:
allowAll = nil // For anything that isn't using singleDobj
skipsCheckAllSafeties = true // Required
There’s obviously a lot of improvement that will be needed here, but this is a working demo of the simplest version. As suggested, it also explains why certain Thing
s are being excluded from X ALL, which is primarily the main appeal for something like this.
Let me know what y’all think!
on X ALL, I learned the hard way the rule “thou shalt not put too many decorations in a room”; now a different angle, the output of X ALL in a room tend to became ugly when there’s many items in scope (an ugliness whose can often show up when the player is scratching his head on a puzzle…); so isn’t a bad idea allowing examining ALL, but under specific scopes (in simpler terms, X INVENTORY, X [ROOM] ) ?
Best regards from Italy,
dott. Piergiorgio.
In my view ALL should only apply to items that are listed in the appropriate place (eg the location for GET ALL). If it is mentioned in the description, but not listed, it is excluded from ALL - even if the item can be picked up. I would also exclude NPCs. This is the default for QuestJS, and I think for Quest 5.
So I can’t remember if you’re an Adv3 author or an Adv3Lite author, but I just discovered last night that you can make certain objects ignore TAKE ALL and other specific X ALL commands in Adv3Lite; not sure about Adv3 though.
Because nothing is stopping me now from putting a comedic amount of interactive items in a room!! I know now to make the specifically-decorative items ignore X ALL.
Even if that gives away the fact that they’re decorative, it’s no different than games were all items in the room are necessary for a puzzle. I just wanna support the TAKE ALL players out there, even if it’s not something I really do when I play.
Sorry I didn’t mean to reply twice in a row here; the quote function doesn’t work with a post that is being edited.
I just wanted to say that something like this is really ideal for me. Have items that can be picked up, but if you have to specifically examine their container to see them listed (in other words, they’re not listed when looking around the room), then they’re probably room-dressing, take-able or not, and will ignore X ALL.
EDIT: I have some ideas of how to implement this, too, actually. Might try to see if I can get it working in Adv3 as well, tho I know almost nothing about it, lol.
Also just to be clear: The code I’m posting here isn’t meant to be a “solved solution”; just experimental implementations to explore how some ideas might be done. Also, I love a coding challenge!
Alright, everybody, I’m back!!!
(Dumps a giant, heavy, cardboard box onto the table)
I have momentarily left the safety of the bustling Adv3Lite world to explore the deep, rich history of the larger Adv3 region, and have assembled some code that should work in both libraries, and implement the following idea…
TRINKETS
A Trinket
is a kind of Thing
, which can be placed on tables, in boxes, under desks, or otherwise stored in a container of some kind.
As long as they are in a container, and not directly in the outermostRoom
itself, they will only be generically-listed1 when examining or searching their container. They can be interacted with, just like any other Thing
! However, their listing behavior also means they do not clutter up the output after looking around the room!
Additionally, as long as they are not in the player’s inventory (or directly in the outermostRoom
), they will also be ignored when X ALL is used!
(The player inventory clause allows them to qualify for DROP ALL)
1 “generically-listed” meaning listed like:
You see a puzzle cube, bottle, and singularity here.
rather than:
The lamp rests stoically upon the desk.
To use, simply include the following code to your game, be in Adv3 or Adv3Lite!
(This code page is a lot shorter, compared to the previous one I posted in this thread!)
Trinkets.t
/* TRINKETS
* by Joseph Cramsey
*
* A Thing that can be interacted with, but has limited listing
* behaviors, and is excluded from X ALL actions.
*/
class Trinket: Thing {
// This property works in both Adv3 and Adv3Lite
isListed = (!canBeIgnored())
// Adv3Lite properties
inventoryListed = true
lookListed = (isListed)
examineListed = true
searchListed = true
// Adv3 properties
isListedInInventory = true
isListedInContents = true
// This check works in both Adv3 and Adv3Lite!
hideFromAll(action) {
return canBeIgnored();
}
// The bit that handles the actual logic.
// This ALSO works in both Adv3 and Adv3Lite!
canBeIgnored() {
// If we are exposed, in the middle of the room, then
// we cannot be ignored.
if (location == getOutermostRoom()) {
return nil;
}
// Allow actions like DROP ALL if we are being carried.
if (isHeldBy(gPlayerChar)) {
return nil;
}
// Otherwise, pay us no mind in X ALL.
return true;
}
}
now all we need is an #ifdef giving full universality to this contrib library (I call extensions “contribs” for historical if-archive reasons)
Best regards from Italy,
dott. Piergiorgio.
The version I had made before posting it here actually had an #ifdef
option, but it felt a bit clunky to ask an author to include the necessary #define adv3Lite <true/nil>
line, each time they wanted to use trinkets in a new project. I sorta figure that if someone really wanted to slim it down, they could just remove the unnecessary properties and keep that file handy on their machine (which is why I added comments).
I could add it back in, though, if you think it would be better! I’m always open to feedback!
the trouble is that there’s no #define internal to adv3 or adv3lite that ID uniequivocally if the library used is adv3 or adv3lite (just done a quick check, perhaps something get unnoticed…)
Best regards from Italy,
dott. Piergiorgio.
You’re right; there isn’t; I checked when I was adding the feature originally, lol.
What I meant was like the author would have to write their own #define adv3Lite ...
line in their project (probably before their includes). But if an author happened to forget, then there would be annoying compilation errors, and this would all be for a relatively-minor neatness factor.
It would be really excellent to have an internal ID defined for this sort of thing. The problem is it’s not very future-proof. We are not guaranteed to only have Adv3 and Adv3Lite. There’s already “Adv3Liter”, and other potential options, as time goes on.
It might be nice to have some internal ID like #define __standardLibID <integer>
, where:
-
#define __standardLibID 0
is “no library” -
#define __standardLibID 1
is “Adv3” -
#define __standardLibID 2
is “Adv3Lite” -
#define __standardLibID 3
is “Adv3Liter” -
#define __standardLibID 4
is some potential future library - etc, etc
That way, we could check the value of __standardLibID
to see what library is currently being used at compile time.
Concur and agree, with a little variation, until we figure Roberts’s status, and by this, figure the future of Adv3, is wise to use #undefined for Adv3 (I think that Eric can agree on adding an internal ID…)
Best regards from Italy,
dott. Piergiorgio.
I can agree on doing it, I’m just not sure how to in a way that would be much use in practice. I’ve gone part way by having advlite.h define the constant ADV3LITE_H (where previously, in common with adv3, it defined ADV3_H), but the catch here is that this will only take effect in source files that include advlite.h, which means a source file already to know which library it’s meant to be working with before the constants ADV3LITE_H or ADV3_H are available to test for conditional compilation. I suppose you could issue an extension that tested for these constants along with instructions on including the appropriate header file, but it might be easier for all concerned simply to issue two versions of the extension.
Note that you can disable ALL for all but a handful of verbs (at least in adv3Lite, but I suspect the same is true in adv3 as well) by setting gameMain.allVerbsAllowAll
to nil. If you set this to nil, only the basic inventory management verbs (TAKE, TAKE FROM, DROP, PUT IN, PUT ON) will allow ALL, and other verbs will simply respond with an error (“‘All’ isn’t allowed with that verb”).
I hope you don’t mind but I’ve now included a modifed version of this in the adv3Lite library (for the upcoming version 1.61) under the name MinorItem
.
I gave it the Unlicense on GitHub specifically so that this could be possible! I am beyond honored!
Also curious to see what modifications you’ve made!
Well, you could look at extras.t on GitHub, but I’ve copied the definition of MinorItem below:
Definition of MinorItem
/*
* A MinorItem is an unobtrusive and possibly unimportant portable object that's worth
* implementing in the game but sufficiently minor as to be not worth mentioning (in response to X
* or FOO ALL) unless it's either directly held by the player character or directly in the
* enclosing room. (Based on Joey Cramsey's Trinket class).
*/
class MinorItem: Thing
/*
* We're listed in response to a LOOK command only if we're not fixed in place and we can't be
* ignored for the current action or our current location.
*/
lookListed = (!canBeIgnored(gAction) && !isFixed)
/* We're excluded from a FOO ALL action if we can be ignored for FOO. */
hideFromAll(action)
{
return canBeIgnored(action);
}
/*
* We can be ignored for action unless we're directly in our enclosing room or directly in the
* player character's location or carried by the player character or, if desired, the action
* is TAKEFROM or PutXX
*/
canBeIgnored(action)
{
/*
* If we are exposed, in the middle of the room or the actor's location (e.g. a
* platform or booth), then we cannot be ignored.
*/
if (location is in (getOutermostRoom(), gActor.location))
return nil;
/* Allow actions like DROP ALL if we are being directly carried. */
if (isDirectlyHeldBy(gActor))
return nil;
/* Allow an explicit TAKE FROM or PUT somewhere if our allowTakeFromPut flag allows it. */
if(action is in (TakeFrom, PutIn, PutOn, PutBehind, PutUnder))
return !includeTakeFromPutAll;
// Otherwise, pay us no mind in X ALL.
return true;
}
/*
* Flag: do we want to be included in TAKE ALL FROM X, and PUT ALL
IN/ON/UNDER/BEHIND X? by
* default we do.
*/
includeTakeFromPutAll = true
;