Bookcase Puzzle

In this script after placing the right books in the proper order into the bookshelf and the door opens.
I want it so that the player is punished by spawning in a monster when he places the books in the wrong order.

Script was originally written by ma"dryyn and I’ve since modified it.

Code so far:

void main()
    object oThief = GetLastClosedBy();

    object oBook = GetFirstItemInInventory(OBJECT_SELF);

    // Spell "SETKH" the name of the mummy to be spawned.
    object oS = GetObjectByTag("BOOK_S",0);
    object oE = GetObjectByTag("BOOK_E",0);
    object oT = GetObjectByTag("BOOK_T",0);
    object oK = GetObjectByTag("BOOK_K",0);
    object oH = GetObjectByTag("BOOK_H",0);

    location lLoc = GetLocation(GetWaypointByTag("MONSTER_LOC"));

    if ((oBook == oH) && (GetIsObjectValid(oBook)))
        oBook = GetNextItemInInventory(OBJECT_SELF);
        if ((oBook == oK) && (GetIsObjectValid(oBook)))
            oBook = GetNextItemInInventory(OBJECT_SELF);
            if ((oBook == oT) && (GetIsObjectValid(oBook)))
                oBook = GetNextItemInInventory(OBJECT_SELF);
                if ((oBook == oE) && (GetIsObjectValid(oBook)))
                    oBook = GetNextItemInInventory(OBJECT_SELF);
                    if ((oBook == oS) && (GetIsObjectValid(oBook)))
                        SetLocked(GetObjectByTag("PUZZLE_DOOR"), FALSE);
                        ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect
                        ( VFX_IMP_KNOCK ), GetObjectByTag("PUZZLE_DOOR"));
                        DelayCommand(2.5, AssignCommand(GetObjectByTag("PUZZLE_DOOR"), ActionPlayAnimation(ANIMATION_DOOR_OPEN1)));
                        CreateObject(OBJECT_TYPE_CREATURE, "mumwarr001", lLoc);

As it stands now, the spawn doesn’t work.
Any help would be greatly appreciated. :wink:


As it stands, your condition to spawn is impossible, because by the time you are placing the last book, you cannot fail to be placing it correctly. If all other books are correct up until placing the last one, the last book will obviously be placed correctly.

Add an integer check at every stage, and if and. when your conditional checks fail, that integer is set and checked at the end to spawn the creature. Do not bury the spawning so deep where the condition will never be met.

(I’m not at computer to type code.)

EDIT: I don’t think you can rely on this to get an “order” as such either. :thinking: This would need a complete rewrite, and I’m not sure this will work as it stands, regardless of the spawning. I would say consider designing a GUI to handle the objects, but I’m not sure you have that in NWN1?

I agree with @Lance_Botelle here. To get an idea of something very similar to what you want, take a look at the addendum to Custom Content Challenge September 2014: The Mists of Ravenloft. The download you want is ccc_2014_09_addendum_traps.7z, so there is no need to download the whole CCC if you don’t want. Take a look at this little pdf manual (also included in that download) to get an idea of what my scripts do and how to use them. With a little work, they should be easy to adapt to what you are trying to do.

TR’s Deadly Candles.7z (754.9 KB)

At the heart of the Deadly Candles the PC has to light 6 candles in a particular order. Failure teleports undead to attack the PC.


Yes, that needs testing. A loop GetFirst / GetNextItemInInventory listing the item names would give proof of concept. It would be interesting to see what happens when books are removed and replaced in a different order.

If the order is reliable, all well and good. Otherwise, the OnDisturbed event could be used to track the order in which books are added.

On further thoughts, I come to the conclusion that apart from a pair of “text appears when” (one to check they have all the books and another when they don’t have enough books) scripts and a script to create the monster when they get it wrong, you could do this via a conversation. What you want is basically a combination lock that creates a monster to attack the PC/party should they get it wrong. That is eminently doable.


it is unreliable to get the correct order with FindFirst/Next (as presented in the inventory screen). It might look well in the inventory screen, but the engine might have a different order. Especially if the player exchange items in the inventory to change the order, FindFirst/Next will probably fail.

1 Like

Ok. Thanks for replying guys.

Yeah I’m afraid I’m not a coder. So some of the concepts you’ve suggested, are lost to me. :frowning: I can cobble together scripts well enough for my own personal use, and usually get away with it, but sometimes I hit a wall. Usually about the time when my ambitions outpace my scripting chops… lol.
I’ll read up on some of your suggestions and see if I learn anything. :nerd_face:

Firstly, the “else” will only be triggered if the last “if” fails and none of the previous “if” statements will cause it to trigger since none of them are linked to that “else” statement.
Secondly, if all of the first four books are in the right order, then if the last book is present at all, it has to be in the right order.
Thirdly, and possibly most importantly, the “GetFirst… GetNext…” commands cannot be relied upon to get the books in the order in which they were placed by the PC. The only way to guarantee the order is to use five different book cases and check each container to see that the correct book has been placed in the correct book case.
What you need is a script for the closing of each book case which runs through all of the book cases and checks firstly that they all have one of the books in them, correct or not. If any book case is missing a book, then the script should do nothing. If all five books are present in the book cases, then the script can check that the correct book is in each case (you could use the tag of the book case or a variable placed on each book case for this).
Written correctly, the same script should be usable for all five book cases.

Remember, you cannot just use GetFirstObjectInInventory since you need to account for the possibility that the PC has placed something entirely different or even several different things in each bookcase, perhaps with the book mixed in with other items. GetItemPossessedBy will probably work better in this situation since you can restrict it to only searching for the books, but you then have to search for all five books in all five book cases, which means you’re looking for 25 objects in all.
There may be an easier way to do what you want. I’ll give it some thought.

Here’s one way of doing what you want, using separate bookshelves. I’ve tested this to be sure it works with my settings. You should only need to make appropriate adjustments to the tags of the bookshelves (or whatever you use as containers).
I suggest you use invisible objects suspended in “midair” along the length of a static bookshelf, so the PC appears to be accessing each section of the book shelf separately.

// checkbooks
// Activates OnClose for five containers.  This same script goes on all five.
// Checks 5 different containers (assumed to be book shelves) to see if each
// shelf contains the correct book.
// Does nothing if at least one book shelf has none of the books.
// Activates a reward and destroys the books if all the books have been placed
// in the correct shelves.
// Activates a penalty if all books have been placed on shelves but they are
// not all on the correct shelves.

void main()
// Create an object variable which will be used for at least two different
// things, but we'll give it a name meaningful to at least one of those uses.
  object oMonster;

// Find out who closed the bookshelf.
// This is not needed if we're not using the PC to reference anything else.
  //object oPC=GetLastClosedBy();

// Find all of the bookshelf placeables.
  object oBS1=GetObjectByTag("Bookshelf1");
  object oBS2=GetObjectByTag("Bookshelf2");
  object oBS3=GetObjectByTag("Bookshelf3");
  object oBS4=GetObjectByTag("Bookshelf4");
  object oBS5=GetObjectByTag("Bookshelf5");

// No need to go any further if any of the bookshelves are empty.
  if(!GetIsObjectValid(GetFirstItemInInventory(oBS1))) return;
  if(!GetIsObjectValid(GetFirstItemInInventory(oBS2))) return;
  if(!GetIsObjectValid(GetFirstItemInInventory(oBS3))) return;
  if(!GetIsObjectValid(GetFirstItemInInventory(oBS4))) return;
  if(!GetIsObjectValid(GetFirstItemInInventory(oBS5))) return;

// Find each of the books so they can be removed once the puzzle is solved.
  object oBook1=GetObjectByTag("BOOK_S");
  object oBook2=GetObjectByTag("BOOK_E");
  object oBook3=GetObjectByTag("BOOK_T");
  object oBook4=GetObjectByTag("BOOK_K");
  object oBook5=GetObjectByTag("BOOK_H");

// Find out who or what has each of the books.
  object oBC1=GetItemPossessor(oBook1);
  object oBC2=GetItemPossessor(oBook2);
  object oBC3=GetItemPossessor(oBook3);
  object oBC4=GetItemPossessor(oBook4);
  object oBC5=GetItemPossessor(oBook5);

// First check if all of the books are in the inventory of the correct book cases.
  if(oBC1==oBS1 && oBC2==oBS2 && oBC3==oBS3 && oBC4==oBS4 && oBC5==oBS5)
  // Success scripting goes here.

    SetLocked(GetObjectByTag("PUZZLE_DOOR"), FALSE);
    ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect( VFX_IMP_KNOCK ), GetObjectByTag("PUZZLE_DOOR"));
    DelayCommand(2.5, AssignCommand(GetObjectByTag("PUZZLE_DOOR"), ActionPlayAnimation(ANIMATION_DOOR_OPEN1)));
  // It's a do-once-only puzzle, so get rid of the books before exiting.
    return;// All done.
// Get the monster spawn point, to shorten and simplify the CreateObject command later.
  object oSP=GetWaypointByTag("MONSTER_LOC");

// Now for the most complicated part, which is checking that at least one of
// the books has been placed into the wrong book shelf AND that all of the books
// have been placed into book shelves.
  if((oBC1==oBS2 || oBC1==oBS3 || oBC1==oBS4 || oBC1==oBS5) // Book 1 is on the wrong shelf
   ||(oBC2==oBS1 || oBC2==oBS3 || oBC2==oBS4 || oBC2==oBS5) // Book 2 is on the wrong shelf
   ||(oBC3==oBS1 || oBC3==oBS2 || oBC3==oBS4 || oBC3==oBS5) // Book 3 is on the wrong shelf
   ||(oBC4==oBS1 || oBC4==oBS2 || oBC4==oBS3 || oBC4==oBS5) // Book 4 is on the wrong shelf
   ||(oBC5==oBS1 || oBC5==oBS2 || oBC5==oBS3 || oBC5==oBS4))// Book 5 is on the wrong shelf
  // Failure scripting goes here.

  // Create a monster at the monster spawn point.
  // Just for flexibility, I used a variable on the waypoint to tell the script
  // the ResRef of the monster to be spawned.  You can do the same by just
  // giving the waypoint the local string variable MONSTER containing the ResRef
  // of whatever you wish to spawn at that waypoint.
    oMonster=CreateObject(OBJECT_TYPE_CREATURE, GetLocalString(oSP,"MONSTER"),GetLocation(oSP));
    // We should probably announce the nasty guy's appearance with a visual effect.
    ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_FNF_LOS_EVIL_10), GetLocation(oSP));

  // No need for a "return;" because the script will exit anyway.

I hope this helps you to do what you wanted, even though it’s not exactly what you asked for.

Here’s a challenge for you to do on your own: Figure out how to add more containers so you have, for example, 10 containers instead of 5.
If you only allow the first 5 containers to be used to solve the puzzle, then only one “if” test needs to be changed.

Second challenge: Figure out how to add “dummy” books which should not be used in the puzzle but which should also be destroyed once the puzzle has been solved.
(This lets the player spell out different names using different books, but only one name is the solution… unless you want there to be an alternate solution)

Wow! Thank you Melkior! Sorry it took so long to respond. Many thanks. I’ll test this and post back soon.