Differences
This shows you the differences between two versions of the page.
| Next revision | Previous revision | ||
|
en:authors_main:task_editor:advanced:inventory [2026/06/15 21:31] 127.0.0.1 внешнее изменение |
en:authors_main:task_editor:advanced:inventory [2026/09/18 06:51] (current) |
||
|---|---|---|---|
| Line 2: | Line 2: | ||
| ====== Player Inventory ====== | ====== Player Inventory ====== | ||
| - | The QEng engine supports a virtual inventory system for teams or single players. This allows authors to design quests that involve finding and using virtual items (keys, tools, currency). | + | The inventory is a bag of items the player finds during the game and applies to things on the level: a key to a lock, a crowbar to a lid, a coin to a machine. An item is a string key (''hammer123''), and everything you do with it comes down to codes: the player applies an item, and the engine submits a code for them. |
| - | ===== What is Inventory? ===== | + | There are two versions of the inventory, and they live side by side. |
| - | In the player interface, the inventory is displayed as a list of items with images, short descriptions, and action buttons. | + | |
| - | {{ :authors_main:inventory.png?800 |Example of inventory display on a level}} | + | * **The second** — a panel at the bottom of the screen, a bag with a counter, and items applied to **zones** on the task picture. One line in the task script turns it on, and it asks for no room in the task text. This page is about it. |
| + | * **The first** — a list of items inside a block the author sets aside, with two buttons on an item: the eye and "use". It [[#the_first_version|works the way it always worked]] and is described at the end of the page; running games are not deprived of it. | ||
| - | Each item has a unique string identifier (key) that can be added, removed, or activated (used). | + | A level picks one of the two: whichever function the script called is the one that renders. |
| - | ===== How to Display the Inventory on a Level ===== | + | ===== What the Player Sees ===== |
| + | |||
| + | At the bottom of the screen there is a bag button. It shows the total number of items and, separately, "+N" — how many are new; while there are new ones, the bag shakes. | ||
| + | |||
| + | {{ :authors_main:task_editor:advanced:inventory2_bag_en.png?800 |The collapsed bag: how many items in total and how many of them are new}} | ||
| + | |||
| + | The button opens a strip of items; new ones are highlighted yellow and stop being new as soon as the player touches them — hovers, taps, or drags. | ||
| + | |||
| + | {{ :authors_main:task_editor:advanced:inventory2_panel_en.png?800 |The open panel: the strip of items at the bottom of the screen, the new item highlighted}} | ||
| + | |||
| + | There are two ways to apply an item, and they are equal: | ||
| + | |||
| + | * **drag** the item onto the place of application; | ||
| + | * **click** the item, then click the place of application. | ||
| + | |||
| + | As soon as the item is "in hand", every place it can be applied to lights up on the level — the **zones**. At the same moment two more receivers appear next to the bag: "use" (no zone, the item on its own) and the eye — look at the item full size. | ||
| + | |||
| + | {{ :authors_main:task_editor:advanced:inventory2_zones_en.png?800 |An item in hand: the zones have lit up on the picture, and next to the bag are the eye and "use"}} | ||
| + | |||
| + | Let go past a receiver or click an empty spot, and the item goes back into the bag. | ||
| + | |||
| + | What came of the application the player sees on the item itself: it fitted — the item jumps green; it did not — it shakes its head red. If a bonus took the item, it dissolves where it was carried to. Until the answer arrives the item pulses in place. | ||
| + | |||
| + | The open panel and the point the player scrolled the strip to survive a page reload: a code is entered, the level changes — the bag stays open at the same place. | ||
| + | |||
| + | The panel is pinned to the bottom of the window instead of standing in the task text, so on a phone the item and the place of application do not have to fit on the screen at once. | ||
| + | |||
| + | {{ :authors_main:task_editor:advanced:inventory2_phone_en.png?400 |A narrow screen: the strip and the receivers are pinned to the bottom and do not scroll away}} | ||
| + | |||
| + | ===== Turning It On ===== | ||
| + | |||
| + | In the **task script** (the field at the bottom of the level): | ||
| - | 1. In the task text (in **"Source"** mode), insert an HTML container where the inventory list should be rendered: | ||
| - | <code HTML> | ||
| - | <div id="inventory_container">Loading inventory...</div> | ||
| - | </code> | ||
| - | 2. In the task script field (at the bottom of the level settings), add the JS call: | ||
| <code JavaScript> | <code JavaScript> | ||
| - | showInventoryItems('inventory_container'); | + | showInventoryItems2({gameId: 1234}); |
| </code> | </code> | ||
| - | ===== Managing Items ===== | + | Nothing else is needed — no block in the task text, no ''id''; the engine draws the panel itself. |
| + | |||
| + | ''gameId'' is the game whose folder holds the item images. It can be omitted, and then the current game is used — but see [[#item_images_after_copying_a_game|copying a game]]. | ||
| + | |||
| + | > **The call must be in the task script.** The engine loads the second version after seeing ''showInventoryItems2'' in the level's script; from the task text, from a bonus, or from the game's common header it will not load it — the function will simply not exist. | ||
| + | > | ||
| + | > And **there must be exactly one call**: it takes the accumulated list of items, so a second one within the same render draws an empty bag. | ||
| + | |||
| + | ===== Items ===== | ||
| + | |||
| + | ==== Where They Come From ==== | ||
| + | |||
| + | An item is given out from a bonus's **"Text after solution"** — in **"Source"** mode: | ||
| - | ==== Adding an Item to the Inventory ==== | ||
| - | Adding items is linked to solving level bonuses (or spanning bonuses). | ||
| - | 1. Create a bonus on the level (e.g., "Find a hammer"). | ||
| - | 2. In the **"Text after solution"** field of that bonus, in **"Source"** mode, write: | ||
| <code HTML> | <code HTML> | ||
| <script> | <script> | ||
| Line 33: | Line 67: | ||
| </script> | </script> | ||
| </code> | </code> | ||
| - | Once the team solves this bonus, the item with key ''hammer123'' will appear in their inventory. | ||
| - | ==== Item Images ==== | + | It is taken away from there too, by a bonus of a later step: |
| - | For the item ''hammer123'' to render correctly, upload two image files to the CDN with strict filenames: | + | |
| - | * **''item_hammer123.jpg''** — the item thumbnail displayed in the inventory list. | + | |
| - | * **''item_hammer123_big.jpg''** — the detailed full-size image displayed when clicking the eye icon (detailed view). | + | |
| - | ==== Removing an Item from the Inventory (Usage or Loss) ==== | ||
| - | To remove an item from a team (e.g. when giving it to a character or using it to unlock a door): | ||
| - | 1. Create a bonus that is solved during the game. | ||
| - | 2. In its **"Text after solution"** field, in **"Source"** mode, write: | ||
| <code HTML> | <code HTML> | ||
| <script> | <script> | ||
| Line 50: | Line 76: | ||
| </code> | </code> | ||
| - | <note important> | + | > The engine runs the texts of solved bonuses **afresh on every redraw of the page**. The list of items is therefore stored nowhere: it is assembled from scratch every time out of what has already been solved. The server knows nothing about items. |
| - | **Important:** If an item was added via a **spanning** (global) bonus, it cannot be removed by a regular level bonus. It must be removed via a spanning bonus with a higher sequence number. | + | |
| - | </note> | + | The same fact gives the opposite trick: hand out items not from bonuses but from the **task script itself**, working out from the entered codes what should be in the bag right now. The shelf example level is built that way — a book can be put on the shelf, taken off, and swapped for another, and no separate codes are needed for it. |
| + | |||
| + | > **Spanning bonus.** An item given by a spanning bonus cannot be taken away by an ordinary level bonus — only by a spanning bonus with a higher sequence number. | ||
| + | |||
| + | **An item does not travel to the next level by itself.** A level's bonus does not run on another level, so the item is not there either. A thing carried through the whole game is given out by a **spanning** bonus. | ||
| + | |||
| + | ==== Item Images ==== | ||
| + | |||
| + | An item's picture is looked up by file name: **''item_<key>''** is the icon in the strip, **''item_<key>_big''** is what the eye opens. | ||
| + | |||
| + | There is no need to upload both by hand. Upload **one** picture named ''item_hammer123.png'' to the game's files: if it is **512 pixels or more on both sides**, the engine splits it into the icon and the large version itself. Smaller, and it stays a single file, and the eye shows that same one. | ||
| + | |||
| + | In the list of uploaded files such a picture gets a ready-made line, ''addInventoryItem('hammer123')'' — a click copies it to the clipboard. That is what goes into the bonus: an item is connected by its key, not by a file address. | ||
| + | |||
| + | The engine keeps such a file's name as it is, even with "Keep the name" unchecked: the inventory looks the picture up by name. The key is taken from the name together with its case — it is exactly the string that goes into ''addInventoryItem()''. | ||
| + | |||
| + | === Format === | ||
| + | |||
| + | By default the engine looks for ''.jpg''. Pictures in another format — including the transparent png the slot and the shadow in the panel were made for — are declared once per game, in the **"Common HTML header"**: | ||
| + | |||
| + | <code HTML> | ||
| + | <script> | ||
| + | setInventoryImageExt('png'); | ||
| + | setInventoryBigExt('png'); | ||
| + | </script> | ||
| + | </code> | ||
| + | |||
| + | The icon and the large view are declared separately: they may be in different formats. A one-off exception goes as the second argument: | ||
| + | |||
| + | <code JavaScript> | ||
| + | addInventoryItem('map7', {icon: 'png', big: 'png'}); | ||
| + | </code> | ||
| + | |||
| + | The extension is **not part of the key**: the key is the code that goes to the server. | ||
| + | |||
| + | === Item Images After Copying a Game === | ||
| + | |||
| + | Item images are taken from the folder of the game whose number you passed. Copying a game does not copy its files, so in the copy the pictures are gone — unless ''gameId'' is named explicitly, or if it points at the copy itself. | ||
| + | |||
| + | Bind the inventory to the game the pictures were uploaded to: | ||
| + | |||
| + | <code JavaScript> | ||
| + | showInventoryItems2({gameId: 3493}); | ||
| + | </code> | ||
| + | |||
| + | The same number can be set once for the whole game, in the **"Common HTML header"** — it is copied together with the game: | ||
| + | |||
| + | <code HTML> | ||
| + | <script> | ||
| + | setInventoryGameId(3493); | ||
| + | </script> | ||
| + | </code> | ||
| + | |||
| + | ===== Zones of Application ===== | ||
| + | |||
| + | A **zone** is a place on the level an item is applied to: a door, a lock, a merchant, a shelf slot. A zone has a code. | ||
| + | |||
| + | When the player applies an item to a zone, the engine submits a **compound code: the zone's code plus the item's key**. A crowbar (''crowbar'') on the ''lid'' zone is the code ''lidcrowbar''. That is the code the bonus which opens the lid must have. | ||
| + | |||
| + | {{ :authors_main:task_editor:advanced:inventory2_apply_en.gif |The crowbar on the lever: the code is the zone's name plus the item's key}} | ||
| + | |||
| + | A zone with no code behind it simply answers "did not fit", and the item returns to the bag. There is no need to invent "empty" zones: a zone with no matching item is harmless. | ||
| + | |||
| + | Applying **without a zone** — "use" next to the bag — submits a code equal to the item's key (the same as in the first version). | ||
| + | |||
| + | > **The zone's code and the key are glued together with no separator**, so keep the names apart yourself: the zone ''slot'' with the item ''1book'' and the zone ''slot1'' with the item ''book'' give one and the same code. | ||
| + | |||
| + | The engine does not check on the player's side which item fits which zone, and it will not: the right answer would then be lying in the page source. Any item on any zone is just a code, and it is the server that answers it. | ||
| + | |||
| + | ==== Declaring a Zone ==== | ||
| + | |||
| + | Four ways, and all of them are about the same thing — a point and a radius around it. They differ only in where the point comes from. | ||
| + | |||
| + | === A Point on a Picture (Inline SVG) === | ||
| + | |||
| + | The main way when a picture has several places of application. In the task text, in **"Source"** mode: | ||
| + | |||
| + | <code HTML> | ||
| + | <svg viewBox="0 0 800 600"> | ||
| + | <image href="https://cdn.qeng.org/f/g/1234/room.jpg" width="800" height="600"/> | ||
| + | <circle data-inventory-zone="door" cx="410" cy="255" r="40" fill="none"/> | ||
| + | <circle data-inventory-zone="chest" cx="96" cy="470" r="55" fill="none"/> | ||
| + | </svg> | ||
| + | </code> | ||
| + | |||
| + | The coordinates live in the ''viewBox'' system, that is, they are tied to the picture rather than to the screen: phone, desktop, rotation, zoom — the point stays in the same place of the drawing. Nothing has to be recalculated. | ||
| + | |||
| + | ''r'' is the radius of application **in the picture's units**. On a small screen the picture shrinks, and the zone would shrink with it — so the engine raises the hit area to the size of a finger by itself. | ||
| + | |||
| + | Not only ''circle'' works: any shape's bounding box is taken. | ||
| + | |||
| + | > A picture inserted as ''<img>'' cannot be marked up this way: an external svg has no content the engine can reach. For a picture in ''<img>'' use the percentages below. | ||
| + | |||
| + | === Percentages on a Picture === | ||
| + | |||
| + | The cheapest way when you would rather not bother with svg: | ||
| + | |||
| + | <code HTML> | ||
| + | <img src="..." data-inventory-zones="door:41,42 chest:12,78,6"> | ||
| + | </code> | ||
| + | |||
| + | After the colon — the zone's code, then percentages of the picture's own size: x, y and (optionally) the radius. One attribute can hold as many zones as you like, separated by spaces. | ||
| + | |||
| + | An icon of its own cannot be set this way — only one common to every zone of that host. If each needs its own, use ''data-inventory-zone'' on separate elements or ''addInventoryZone()''. | ||
| + | |||
| + | === Any Element of the Page === | ||
| + | |||
| + | <code HTML> | ||
| + | <div data-inventory-zone="npc">The merchant</div> | ||
| + | <img src="..." data-inventory-zone="lock"> | ||
| + | </code> | ||
| + | |||
| + | The hit area is the element's whole rectangle. This is the answer for "the zone is a block of text, a whole picture, or a button". | ||
| + | |||
| + | === From a Script === | ||
| + | |||
| + | A zone that is not on the level to begin with is declared by a call — in the task script or in a bonus's "Text after solution": | ||
| + | |||
| + | <code JavaScript> | ||
| + | addInventoryZone('box', {el: '#room', x: 120, y: 150, r: 45}); | ||
| + | </code> | ||
| + | |||
| + | The first argument is the zone's code. Then: | ||
| + | |||
| + | ^ Field ^ What it means ^ | ||
| + | | ''el'' | the host: a selector or an element. **Required** — it sets the coordinate system | | ||
| + | | ''x'', ''y'' | the position inside the host; without them — the element's centre, and the hit area is its whole rectangle | | ||
| + | | ''r'' | the radius of application; without it — the size of the icon, raised to a finger | | ||
| + | | ''icon'' | the zone's own icon; without it the common one | | ||
| + | |||
| + | **The host decides the units**: an inline svg means its ''viewBox'' coordinates, any other element means percentages of its rectangle (the radius, percentages of the shorter side). The same ''{x: 150, y: 262}'' means different things on an svg and on a picture. There are no absolute screen coordinates on purpose: they would slide away on the very first different screen. | ||
| + | |||
| + | Calling again with the same code does not start a second zone but **updates the existing one**. That also means a zone can be moved: recalculate the position and call again. | ||
| + | |||
| + | To remove one — ''removeInventoryZone('door')''. This call takes down a zone from the markup too, but only until the next auto update: the html comes from the server again, and the zone is back. Such a zone has to be removed where everything else on the level is removed — with a [[en:authors_main:task_editor:advanced:edit_templates|template on the server]]. | ||
| + | |||
| + | A zone from a script **overrides** a zone from the markup with the same code. | ||
| + | |||
| + | ==== An Icon of Your Own ==== | ||
| + | |||
| + | By default every zone gets one common semi-transparent icon. Your own is set with the ''data-inventory-zone-icon'' attribute in the markup or the ''icon'' field of ''addInventoryZone()'', and its kind does not have to be declared — the engine tells them apart itself: | ||
| + | |||
| + | <code HTML> | ||
| + | <circle data-inventory-zone="door" data-inventory-zone-icon="fa-lock" .../> | ||
| + | <circle data-inventory-zone="gate" data-inventory-zone-icon="/f/g/123/door.png" .../> | ||
| + | <circle data-inventory-zone="safe" data-inventory-zone-icon="🔑" .../> | ||
| + | </code> | ||
| + | |||
| + | * ''fa-lock'' — a font-awesome glyph; | ||
| + | * a path or an address — a picture of your own; | ||
| + | * anything else — just a character. | ||
| + | |||
| + | ==== Zones That Are Not There Yet ==== | ||
| + | |||
| + | Which zones exist usually depends on what the player has already done: an opened crate has no zones, and the machine's slot appears together with the coin. The markup for that is convenient to assemble with [[en:authors_main:task_editor:advanced:edit_templates|templates]], ''{% match bonus_N %}'' — they are computed on the server, so the page source under the player holds only the layers and zones that are visible right now. | ||
| + | |||
| + | Templates work the same way in the task script, but the template lines are commented out with a double slash — the engine strips it itself: | ||
| + | |||
| + | <code JavaScript> | ||
| + | // {% match bonus_2}:{ | ||
| + | // {%= leverdncrowbar}:{ | ||
| + | addInventoryZone('leverup', {el: '#quest_room', x: 288, y: 305, r: 45}); | ||
| + | // %} | ||
| + | // {%= ?}:{ | ||
| + | addInventoryZone('leverdn', {el: '#quest_room', x: 315, y: 112, r: 45}); | ||
| + | // %} | ||
| + | // %} | ||
| + | </code> | ||
| + | |||
| + | ===== Worth Knowing ===== | ||
| + | |||
| + | * **An item's key is a code.** Dots, quotes, and extensions must not go into it. | ||
| + | * **Applying is always silent:** nothing appears in the answer form. All the feedback is on the item itself and on the zone. | ||
| + | * **The engine reads the verdict out of the "Previous answers" block.** With monitoring switched off on the level the item simply returns to the bag in silence — no green, no red. | ||
| + | * **The same wrong code does not go to the server a second time.** The item comes back at once, with the same verdict as last time — no attempt is spent. | ||
| + | * **A hidden host is no zone.** A zone on an element inside a collapsed ''<details>'', on ''display: none'', or on a picture that has not loaded yet will not appear until the host shows itself. The engine recounts on its own: on a window resize, on a picture's load, and at the moment the player picks an item up. | ||
| + | * **While an item is in hand, a zone's icon lies over the page** and intercepts the click on whatever is beneath it — including your own button. | ||
| + | * **A bonus's synonyms are a state.** A bonus's value in a template is its **last** entered answer, and each new synonym replaces the previous one. That is why the lever goes back and forth and the book is put up and taken down: two synonyms on one bonus instead of two bonuses. | ||
| + | * **A bonus description marked ''%hide%''** is not in the page source until it is solved — the chain of applications cannot be peeked at. | ||
| + | * A bonus hidden by the engine is printed **without** the answer entered into it. If a script needs that answer, hide the bonuses block with a style rather than with the mark. | ||
| + | * The panel lives outside the auto-updating part of the page: somebody else's code or a new hint will not close it and will not take away the selected item. | ||
| + | * An open panel pushes the bottom of the page away. Your own block pinned to the bottom will argue with it. | ||
| + | |||
| + | What the inventory does not have: **quantity** (an item is either there or not — "three coins" are made of three items or three codes), sorting, and dragging an item onto an item. | ||
| + | |||
| + | ===== Examples in the Game ===== | ||
| + | |||
| + | * [[https://qeng.org/game.php?jump_to&gid=3493&task_id=83700|Inventory: the room]] — items are found and applied to points on a picture: a crowbar, a lever, a lock, a lid, a coin into a machine. Zones from a script, scene layers from templates. | ||
| + | * [[https://qeng.org/game.php?jump_to&gid=3493&task_id=83701|Inventory: the shelf]] — items are not handed out by bonuses at all: what is in the bag and what stands on the shelf is worked out by the script from the entered codes every time. | ||
| + | |||
| + | Both levels carry a "How is it done?" section inside them with a walkthrough. | ||
| + | |||
| + | ===== The First Version ===== | ||
| + | |||
| + | It works the way it always worked. The difference is that the inventory is rendered inside a block the author sets aside, there are no zones, and a selected item has two buttons — the eye and "use". | ||
| + | |||
| + | {{ :authors_main:inventory_en.png?800 |The first version's inventory: items arrive for solved bonuses}} | ||
| + | |||
| + | {{ :authors_main:inventory_item_en.png?800 |A selected item: the eye shows the picture full size, the tick uses the item}} | ||
| + | |||
| + | - In the task text (in **"Source"** mode) — a block where the list should be shown: | ||
| + | <code HTML> | ||
| + | <div id="inventory_container">Loading inventory...</div> | ||
| + | </code> | ||
| + | - In the task script — the call: | ||
| + | <code JavaScript> | ||
| + | showInventoryItems('inventory_container'); | ||
| + | </code> | ||
| + | |||
| + | The second parameter is the same binding of pictures to another game: ''showInventoryItems('inventory_container', 1234)''. | ||
| + | |||
| + | Items are given and taken the same way, with ''addInventoryItem()'' and ''removeInventoryItem()'' in a bonus's "Text after solution". The "use" button submits a code equal to the item's key. | ||
| + | |||
| + | {{ :authors_main:inventory_use_en.gif |An item arrives for a bonus, and "use" types its code for the player}} | ||
| + | |||
| + | Example in the game: [[https://qeng.org/game.php?jump_to&gid=3493&task_id=68367|Inventory]]. | ||
| - | ==== The "Use" Button ==== | + | ===== See Also ===== |
| - | When a player clicks the **"Use"** button next to an item in their inventory, the engine automatically attempts to submit a code matching the item's key. | + | |
| - | * For example, using the item ''hammer123'' will automatically submit the code ''hammer123'' to the engine's answer field. | + | |
| - | ===== Demonstration ===== | + | * [[en:authors_main:task_editor:advanced:edit_templates|Templates]] — ''{% match bonus_N %}'' for a scene and for zones that depend on the state of the level. |
| - | A live example of a level with inventory configuration can be viewed in the [[https://qeng.org/game.php?gid=3493&tid=68367|Demo game for authors]]. | + | * [[en:authors_main:task_editor:advanced:composite_codes|Composite codes]] — the level's final code, assembled from parts. |
| + | * [[en:authors_main:task_editor:advanced:author_scripts|Interactive elements (scripts)]] — the other functions available in the task script. | ||