Inactive Books

B!0HAX

Member
Contributor
World
Cervator edit: Added header, moved to Incubator Modules
Skaldarnar edit: Changed Scope to Mod, opened for new curator.

Name: Books and Bookcases
Summary: Adds books and bookcases to the world, allowing users to read (and write?) books as well as storing them in bookcases
Scope: Mod
Current Goal: Enhance book UI, add/edit text to books, maybe dynamically select bookcase block texture based on quantity
Phase: Implementation
Curator: Awaits new curator! (old curator: B!0HAX)
Related: #238

Hello... (to everyone reading this)
Im going to introduce the Books that can be read in-game and stored in bookshelves...
(Bookshelves may only store 'Books' and shows a .png depending on the 'nº of books' stored in it)

I have divided the approach into various "sections" so I can develop it & tackle it in easy little "steps"
(basic > +advance)

[1] Bookshelf : container
[2] Bookshelf : Empty & wContent (.png) Show if empty or has content [will show nº books that are inside]
[3] Book : define what a book is (good for next step) and create the item (not usable yet)
[4] Bookshelf : define that only book.items may be stored inside.
[5] Book : Making it usable and pop a GUI on use (preferably recalling a basic content) (and playing a book sound)
[6] Book : Advance GUI : make it recall diff. types of content (TXT & PNG) and make it possible to flip through pages* showing page 1 as a lid with a page, inner book (pages both sides) and a last page (with page and back-lid).

(*): Animation for flipping pages... don't know yet if its worth the try...
Also an animation for the "start.reading" would be nice...

Love tinkering ideas on paper :
The·5min'Draft : Books & Bookshelves (Handmade & Scanned)

DEV. Info.
First of all making the BookShelf (aka BookCase) a container prefab wasn't a hard task (simple chest cloning), also added a custom sound for the action.
I hope today I will be able to sit for a while and try to do [2]...

FEEDBACK (of any style) is appreciated.
 
Last edited by a moderator:

Cervator

Org Co-Founder & Project Lead
Contributor
Design
Logistics
SpecOps
We discussed this on IRC yesterday and I liked the idea, great concept that shouldn't be terribly hard to implement. Trickiest part might be the bookcase block showing a different number of books. We discussed metal ingots on IRC a while back and it seemed that making 16 (x material) different block PNGs might be the best option. That could also allow the actual quantity to be stored in one of the secondary block variables (planned, not yet implemented) - not sure if that's an advantage over using an entity for the lookup (faster for world rendering to just read an index value?)

Looking forward to seeing this progress :)
 

B!0HAX

Member
Contributor
World
The 17 Bookshelf textures

Having finished the 17 textures for the block.Bookshelf (Empty + 16 for Books)
The next step arrives...
[2] Bookshelf Content :
AIM : depending on nº of books recall the correct image, so it shows up the correct .png for the case.
I will sit later on with the c0de and see how I can attempt to check the contents inside the bookshelf .
(I actually have no clue right now) :]

Some Samples:


First I will try to simply check if empty or has content, and show "BookShelf or BookShelf16" maybe with that approach I will be able to extrapolate for different cases (different nº of books)
 

Attachments

B!0HAX

Member
Contributor
World
Readable Books *GUI IDEA*

What do you guys think about this book texture for the reading.GUI ?
Its just an Idea (doesn't mean it will be the final).
 

Attachments

Cervator

Org Co-Founder & Project Lead
Contributor
Design
Logistics
SpecOps
I personally would suggest delaying that part and working on making a book UI work instead, that'll get you into making a BookComponent to hold titles / content / etc and should help you get more familiar with the ES :)

We currently don't have any block in the world that renders based on dynamic data, so I'm somewhat wondering myself what's the best architectural approach for that. There are three approaches I can think of, fleshing out what I mentioned earlier a bit more:

1) Make a BookcaseComponent to attach to a BookcaseBlock in the world, storing the number of books (0-15, not 0-16 IMHO), then pick the texture to render dynamically off that data (probably horribly inefficient and infeasible due to the need for speed in the rendering process). Do need an entity for the bookcase one way or the other so you can 'e' click it
2) Split out the bookcase block to 16 full block IDs, change the actual primitive byte in the world when a book is added /removed. Still need an entity to interact with the bookcase
3) Base the quantity selection on the liquid nibble, which will be refactored into a secondary resource counter down the road (liquid, books, gas, whatever) - this is stored for every block so more easily available during rendering, but probably still not as fast as pure primitive block IDs. We need to support rendering different water levels eventually, so we will need a lookup one day. Unless that becomes a series of block IDs for every possible liquid level / orientation too.

Not really sure where to go architecturally on the rapid growth of block IDs / textures vs secondary lookups for special "blocks" or manipulation of fewer textures into more options (colored grass is a probably a better example there). But maybe tens of thousands of block IDs and manipulated textures isn't so much of a problem since we're low res and likely not capped by video card memory issues? As long as we plan for it (big enough block ID data type)

Examples like stairs or stacks of ingots come to mind when thinking about explosive unique texture / block ID concerns (mainly due to materials - bookcases too could possibly be made out of different types of wood one day?)
 

B!0HAX

Member
Contributor
World
Leaving the "Bookshelf" aside to make the actual book and later on the GUI (this will help for the future Bookshelf GUI also)

BookComponent.java
Code:
package org.terasology.components;
import org.terasology.entitySystem.AbstractComponent;

public final class BookComponent extends AbstractComponent 
{
    public enum BookType {
    	Blank,
    	Contents,
    	Recipe
    }
    public BookType type; 
}
Heres what I added to ItemFactory:
Code:
import org.terasology.components.BookComponent;
import org.terasology.components.BookComponent.BookType;

public EntityRef createBlankBook()
    {
    	EntityRef item = entityManager.create();
    	ItemComponent itemComp = new ItemComponent();
    	itemComp.name = "BlankBook";
    	itemComp.icon = "BlankBook";
    	itemComp.renderWithIcon = true;
    	BookComponent bookComp = new BookComponent();
    	bookComp.type = BookType.Blank;
    	 item.addComponent(itemComp);
    	 item.addComponent(bookComp);
		return item;
    }
(((Q : Why does it only run correctly if there is a "return item" ???)))

And couple of lines to Icon.java

Code:
        Icon blankBookIcon = new Icon();
        blankBookIcon.setAtlasPosition(11, 3);
        icons.put("blankbook", blankBookIcon);
Can't actually test in-game, right now, will leave it for another moment.
 

Cervator

Org Co-Founder & Project Lead
Contributor
Design
Logistics
SpecOps
The return is needed in plain Java, it isn't automagically returned as in some shortcut-happy languages like Groovy :)

Next up: Using the returned EntityRef for Glory and Science!
 

B!0HAX

Member
Contributor
World
giveItem : resolved (giveBook)

Thanks to Immortalius managed to test that the book icon is correct, the thing is the final rendering ain't so good...


GroovyManager.java (CommandHelper class)
Code:
 //"giveItem" : Book
        private void giveBook() {
            ItemFactory factory = new ItemFactory(CoreRegistry.get(EntityManager.class));
            EntityRef item = factory.createBlankBook();

            InventorySystem inventorySystem = CoreRegistry.get(ComponentSystemManager.class).get(InventorySystem.class);
            if (!inventorySystem.addItem(CoreRegistry.get(LocalPlayer.class).getEntity(), item)) {
                item.destroy();
                }
            
            }
 

Attachments

Cervator

Org Co-Founder & Project Lead
Contributor
Design
Logistics
SpecOps
Okay, getting a chance to look at this now that the code is on GitHub :)

https://github.com/bi0hax/Terasology/co ... 976f7690f4

What sticks out to me is that the scenario between books and chests differs - I wonder if something isn't flowing right in the book example. Some thoughts below, all just IMHO since I'm not an expert on our ES yet :)

Chests:
* giveBlock "Chest" to get a placeable block which is registered as a chest.prefab
* Activating 'e' a chest bounces around the entity/event system (having trouble tracing it) and eventually triggers the UI
* UI is open without stalling the screen, loads contents, and allows the player to do stuff

Books:
* giveItem "core:bluebook" to get a non-placeable item which is itself a prefab? (Problem: There should only be a single "book.prefab" and individual books should be defined somewhere and registered with the book prefab, data going into the BookComponent)
* Left-clicking with a book held triggers the UI request directly via ReadBookAction (Problem: I suspect execution flow isn't getting around right - might not "return" right after trying to trigger the UI?)
* UI is supposed to pull up a window using openbook.png (in UIOpenBook), but isn't visible, and even setting UIInventory to use openbook doesn't change anything

I was trying to walk the system with Chests as the example on how the event transmitting system works, but either there are dead ends at the moment or IntelliJ is marking methods as not being in use when they are. Heck, I can't even find ReadBookAction itself being triggered anywhere.

I'd suggest trying to replicate a chest exactly via a bookcase block and making that 'e' pop up the existing chest UI, then replace it. I understand your user case differs (wanting to have the player read a book when held and left-clicked) but think that might be a trickier way to learn the ES properly since it is a new scenario.

But then I need to learn it properly too. Really need Immortius to chime in on exactly what is or isn't in use, figuring some stuff I'm getting caught in just isn't yet done/working :)

(Going through the chest event + UI might be a good way to explain it)

Fake-Edit: So yeah, I just realized I was looking at Inventory UI stuff (hot key 'i') not chest stuff, doh. The openbook texture replaced the inventory screen, not the chest screen. Okay, I'm clearly up too late to really dig into this ;)
 

Immortius

Lead Software Architect
Contributor
Architecture
GUI
Ok, I'll give a rundown of how Chests work as an example. I've started working on some documentation in the wiki, but starting with the advanced user side rather than the how-to-mod side as things are still developing for that end.

Chest Block

Code:
block {
    version = 1

    // Graphics
    faces {
        sides = "ChestSides"
        front = "ChestFront"
        topbottom = "ChestTopBottom"
    }

    entityPrefab = "core:chest"
    stackable = false
    straightToInventory = true
    usable = true
}
The important bit of the Chest block definition are the last four lines. Entity prefab specifies the name of the prefab that is used to generate the entity that governs the block's behavior - this is done by specifying the mod/package holding the prefab ("core") and then the name of the prefab itself ("chest"). Stackable specifies whether the block stacks in an inventory - for chests it cannot as each chest can have a unique contents. Straight to inventory gives it straight to the player on destruction (rather than becoming a physics block), and usable means it can be used with the use key.

Chest Prefab

Code:
{
  "name": "core:chest",
  "Inventory": {
    "itemSlots": [
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0
    ]
  },
  "PlaySoundAction" : {
    sounds = "engine:click"
  },
  "AccessInventoryAction": {}
}
This is the prefab for the chest block. The first line gives the name of the prefab (TODO: derive from filename + asset source instead).

Next is the inventory component, which stores items. The number of item slots is determined by the size of the ItemSlot array, so it is filled with "0" (the null entity id) to bring it up to the desired size (TODO: better method?).

The third and fourth components are "Action" components. These represent actions should should occur when the entity receives an "ActivateEvent".

Note that neither a Health component or Block component is specified - these are added automatically as needed.

Creation

The giveBlock "Chest" command adds into the inventory an item holding the desired block for later placement. In the case of blocks with prefabs, an entity is created from the prefab and related to the item (unless "EntityTemporary" = true in the block definition, in which case entities are only created dynamically as needed for the block and destroyed afterwards (partially implemented).

So the result of the giveBlock "Chest" command looks like this:

Code:
{
   "id": 13,
   "parentPrefab": "core:chest"
},
{
   "id": 12,
   "BlockItem": {
      "placedEntity": 13,
      "blockFamily": "Chest"
    },
    "Item": {
      "icon": "",
      "stackCount": 1,
      "container": 1,
      "name": "Item",
      "baseDamage": 1,
      "usage": "OnBlock",
      "renderWithIcon": false,
      "consumedOnUse": true,
      "stackId": ""
    }
}
With the player's inventory having a slot set to the id 12. The item component specifies all the information about the item and it's usage, while BlockItem relates the item entity to the block type for placement. Various entity lifecycle events are produced by this process (an AddComponent event is sent out as each component is attached to an entity), although nothing is set up to receive them.

Item Usage

When the player clicks, the LocalPlayerSystem picks up the interaction with processMouseInput() (at the moment) and sends it on to processInteractions(). Right click always does an attack action, with damage modified by the held item, while left click checks the "usage" of the item. Depending on the usage a different event is sent to the item, with different information - or if the item is not usable then left click becomes an attack action as well. The possible use actions are UseItemEvent, UseItemInDirectionEvent, UseItemOnBlockEvent and UseItemOnEntityEvent.

The ItemSystem receives and processes each of these events. If the item is successfully used, then a ActivateEvent is sent to the item, triggering all of its actions. The item system has special treatment for items with a BlockItem component - to place the block in the world. If the BlockItem has a placedEntity attached, this entity is given a health component and a block component to tie it to the newly placed block.

In order to support looking up the entity associated with a block in the world, the BlockEntityRegistry receives the AddComponentEvent and RemovedComponentEvents from when an entity is given a Block component, and maintains an index for later lookup (currently a hash map, should use something more appropriate for spatial lookups).

Using a placed chest

So, the big one. Once again this originates from the LocalPlayerSystem - when 'E' is pressed, the block the player is looking at is determined, and if it is usable (in the Block definition) the entity for the block is retrieved from the BlockEntityRegistry and an ActivateEvent is sent to it. It should be noted that if a block is usable but an entity doesn't exist for it, one will automatically be generated.

Because the chest has a AccessInventoryAction component, AccessInventoryAction's onActivate() method is triggered. This just sends an OpenInventoryEvent back to the instigator of the ActivateEvent - in this case the player (this is a little bit bouncy, but AccessInventoryAction cannot open the UI itself, considering the possibility of either NPCs or other players triggering the action). The LocalPlayerSystem receives this event and opens the UIContainerScreen via the GUIManager.

In IntelliJ, the ReceiveEvent methods are marked as unused due to not being called directly (uses reflection shenanigans at the moment)
 

Cervator

Org Co-Founder & Project Lead
Contributor
Design
Logistics
SpecOps
Okay, I've started more thoroughly walking the code to eventually approach the problem with books. In doing so I'm adding comments and doing minor tweaks for clarity when I think they make sense.

Immortius - could you review real quick (pretty minor) and make sure I'm not misunderstanding anything? I've also added a few TODOs where I wasn't sure about something (or expected something might change down the road)

https://github.com/MovingBlocks/Terasol ... 4cfbd0d4cc

I highly encourage this approach for learning in general, as it both helps the learner and the project in having more useful comments to be able to understand stuff more quickly. We just need to make sure to get it reviewed in case the commentator != original author, misunderstandings in comments can be baaad :D
 

Immortius

Lead Software Architect
Contributor
Architecture
GUI
Looks good. :)

Comments:
Code:
//TODO: Any chance to catch the wrong stack if the source inventory has multiple alike items?
int matchedSlot = sourceInventory.itemSlots.indexOf(event.getItem());
This isn't a concern, because what is being search for is the specific item EntityRef - which is unique. Unless an inventory is holding the same item twice, which would suggest an error elsewhere.

Code:
/** The name of this item - TODO: Do we also need stackId? Could base stackable off stackCount > 1 ? */
public String name = "";
Potentially, but name is a UI display field. I don't like to use display fields for logic - maybe a possibility that there would be things with the same name that can stack but not together? Using the prefab name from the EntityInfoComponent may be another option.

Code:
/** Should this item be rendered? TODO: Can this be merged with icon and null == don't render? Or are null checks evil? */     
public String icon = "";
Things like the railgun have an icon but the icon is not rendered in the player's hand. Eventually I imagine it would be a split between not rendering, rendering as a voxelised icon, and rendering with a provided mesh. So maybe an enum?
 

B!0HAX

Member
Contributor
World
The Bookcase now has been made so it stores (exclusively) books.


Still fighting with the UIOpenBook / UIOpenBookScreen...
 

Cervator

Org Co-Founder & Project Lead
Contributor
Design
Logistics
SpecOps
Cool.

Didn't catch that event.getItem() actually returned the entity ref, had thought it would be the name for some reason.

I've removed some todos / tweaked comments further - thanks!
 
What if you could write your own books? Then the one who wrote the books should get a special item that can give you unlimited copies of that book that he/she can give or sell to other players so that they also can read it.

What about that? :)
 

eleazzaar

Member
Contributor
Art
From another thread where it was less topical:

Cervator said:
eleazzaar said:
Cervator said:
Then add bookshelves, dynamically displaying the number of books, bookshelf itself could differ in material, 4 way direction, etc, with 20 different materials, up to 16 books, there's 20 * 16 * 4 = 1280
Make bookshelves 2 sided -- like in a library. That way you only have 2 rotations to worry about. In most cases the opposite side, will be against a wall, so won't matter, but sometimes you'll want a library-type bookshelf look. 1 m is really too deep for only a single row of books anyway.
Good point on bookshelves. Wonder how that would mesh with dynamically displaying the number of books in a bookshelf tho, if each side is unique. That might be overthinking it.
I was assuming that the opposite side of the block would show the same books -- otherwise you aren't saving any block IDs.


:arrow: In thinking about the player making a shelf, then actually writing a book (even if it's just a couple pages worth), it seems a little discouraging to only get 4-12 pixels worth of color, and a whole lot of empty shelf. It would be a significant amount of work to fill a single book-shelf block, even with small stories. And many players will want bookshelves with books for the same way old money stocks rooms with books -- simply to be part of the decorations. So they will probably populate their bookshelves with empty books.

Which is fine, as far as it goes-- but as a player how do you find a bookshelf with something that you can actually read?

I'd suggest a 2-tiered approach:
1) Make a plain non-interactive decorative bookshelf. The images of books will be approximately to minecraft's scale.

2) Emphasize books that players have actually written, by making them bigger, and thus look more important. (see attached) Of course for the books to be bigger, they would neccesarily fit fewer to a block. That's OK, the TS-novelist will have a lot more to show for the effort. Perhaps you couldn't put books on these shelves, unless the books contained written content.


GoldenDragon said:
What if you could write your own books?
If you had looked at the first post, you would see that the whole point is to have books that the player actual writes in.
 

Attachments

overdhose

Active Member
Contributor
Design
World
GUI
Could make some basic books for a standard shelve, including the history of creating Terasology, based on discussions / idea lists etc. I'm quite sure some people less involved might still be amused reading some of that, like a book of woodspeople's questions about minions... I know it would amuse me, more then any generic whatever. Who knows, it might actually get them to check out forums and the like.

Might be out of place by the time the game finishes, but by then you could still make it optional to enable for people who don't really care about books but dislike empty shelves. It could also provide a way to let people read patch notes and progress ingame, which is something I never seen another game do personally, which is a shame.

Just some suggestions, no idea how valid / achievable they actually are, have to admit I still need to check the books at this point, but that doesn't mean I wouldn't enjoy reading the whole minion debate as a Terasology player while i train the minions in walking around walls..

Also, can't bookcases dynamically load a different texture depending on how many books are in them? Look at how Biohax handled the buff icons now, I'm quite sure there is some inspiration there to save ID's. Just make 1 texture file containing all bookcase layouts and load the one that corresponds.

I'll gladly look into the validity of my claim if it sounds plausible.
 

Cervator

Org Co-Founder & Project Lead
Contributor
Design
Logistics
SpecOps
It's plausible - and intended, that was on Bi0hax's list last I checked. We even have the block PNG for each quantified bookshelf somewhere around here, including musings on that book content puzzler. Could be fun to put some project-specific quirks in a few, and I'm sure there's be people happy to just contribute brief written stories for books :)
 

eleazzaar

Member
Contributor
Art
If there are default books, then i think they should be part of the game-mode/mod or whatever you want to call it, not the core.
If i'm playing a DF-style minion-village game, i don't want to have books randomly filled with the lore of a DK-like mod, or the development process.

As a stop-gap whatever is fine, but my concerns about loosing and/or devaluing books that players wrote in-game still stands weather you fill the rest of the bookshelves with blank books or built-in content books
 
Top