Skip to content

Commit

Permalink
feat: story log
Browse files Browse the repository at this point in the history
  • Loading branch information
lunafromthemoon committed Jan 8, 2022
1 parent 34e81b4 commit a39aece
Show file tree
Hide file tree
Showing 17 changed files with 1,254 additions and 184 deletions.
73 changes: 55 additions & 18 deletions docs/docs-page.html
Original file line number Diff line number Diff line change
Expand Up @@ -1949,30 +1949,63 @@ <h2 class="section-heading">Plugin Structure</h2>
<p>As seen in already, the function <strong>onCall</strong>, allows you to call a function from the story script. But there are many other ways to use the <strong>Plugins</strong> besides this specific use case. The <strong>RenJS.Plugin Class</strong> provides handles for each phase of the game where you might need to add your own functions, that you need to simply overwrite.</p>
<p>Let's see in more detail the structure of a Plugin, and what handles can be overwritten.</p>
<pre><code class="language-js">
export default class Plugin implements PluginInterface {
export default class Plugin implements PluginInterface {

protected game: RJS;
protected name: string
protected game: RJS;
protected name: string

constructor(name, game){
this.game = game;
this.name = name
}
// plugin handles
onInit(): void {}
constructor(name, game){
this.game = game;
this.name = name
}

onInit(): void {
// called after everything is loaded (unless lazyloading), before showing any menu
// base plugin does nothing
}

onStart(): void {
// called when new game is started, just before interpreter is called
// base plugin does nothing
}

onLoad(slot,data): void {
// called when loading game data, before loading the game
// "data" parameter can be modified here and change what will be loaded
// base plugin does nothing
}

onStart(): void {}
onLoaded(): void {
// called after loaded data recreated the saved point, just before interpreter is called
// base plugin does nothing
}

onAction(action): void {}
onSave(slot,data): void {
// called just before saving game data to localstorage
// "data" parameter can be modified here and that's what will be saved
// base plugin does nothing
}

onCall(params): void {}
onCall(params): void {
// called from the story, by using "call" action
// base plugin does nothing
}

onLoad(slot,data): void {}
onAction(action): void {
// called before executing every single action, with actual action object
// base plugin does nothing
}

onSave(slot,data): void {}
onEndScene(params): void {
// called when the interpreter runs out of actions, normally when a scene ends without calling another scene
// base plugin does nothing
}

onTeardown(): void {}
onTeardown(): void {
// called when game ends, before returning to the main menu
// base plugin does nothing
}
}
</code></pre>
<p>This is the class definition of the <strong>RenJS Plugins</strong>. As you can see, the constructor receives a name and the <strong>game</strong> object, that will be later available to use from any of the handles. Then handles themselves are empty and do nothing unless you redefine them.</p>
<h3>On Init</h3>
Expand Down Expand Up @@ -2002,9 +2035,11 @@ <h3>On Call</h3>
RenJSGame.addPlugin('helloWorld',HelloWorld)
</code></pre>
<p>In this case, when each line of the function is done executing, the function is technically over, but the action still waits for the timeout callback to finish, and let the game know it can continue with the next action in the script.</p>
<h3>On Save and On Load</h3>
<p>These two handles are called, as you might imagine, when saving and loading the game. RenJS saves games in slots (identification number), locally into the player's browser. The <strong>onSave</strong> handle is called just before the game is saved, and <strong>onLoad</strong> is called when the data is retrieved, but just before setting the game up.</p>
<p>You could use these handles to save and load data to and from an external API, and so have your players data online, making it available in different devices. You might also want to use them to add other information to the saved game, for example, a timestamp.</p>
<h3>On Save, On Load, On Loaded</h3>
<p>These handles are called, as you might imagine, when saving and loading the game. RenJS saves games in slots (identification number), locally into the player's browser.</p> <p>You could use these handles to save and load data to and from an external API, and so have your players data online, making it available in different devices. You might also want to use them to add other information to the saved game, for example, a timestamp, and much more.</p>
<p>The <strong>onSave</strong> handle is called just before the game is saved locally, receiving the slot and data to save. Whatever modification you do to the data parameter will be saved too.</p>
<p>The <strong>onLoad</strong> handle is called when the data is retrieved, just <i>before</i> setting the game up. Just like onSave, it will receive the slot and the data to load. By modifying the data parameter you can change too what will end up being loaded.</p>
<p>The <strong>onLoaded</strong> handle is called just <i>after</i> the loaded data was used to setup the game, but before the interpreter is called. You can use this handle to modify, for example, sprites or graphical elements created when loading.</p>
<pre><code class="language-js">
class CloudSaving extends RenJS.Plugin {

Expand Down Expand Up @@ -2039,6 +2074,8 @@ <h3>On Save and On Load</h3>
</code></pre>
<h3>On Teardown</h3>
<p>This handle will only be called when the player goes from the story to the main menu, usually, after finishing the game. If the player simply closes the game, it will not be executed. Use to clean up any big memory objects you might be using during the story, but avoid saving data or doing anything else that is of big importance for the story.</p>
<h3>On End Scene</h3>
<p>This handle will be called when the interpreter runs out of actions to execute. This can happen only if a scene is finished and no "scene" action or "endGame" action is called. Normally, you don't want this to happen, since there is no normal way for the player to continue, but sometimes this is expected. For example, the point and click plugin waits for a scene to end before enabling the clickable objects for the player, and when the player clicks an object, then another scene will be called.</p>
</section><!--//section-->

<section class="docs-section" id="transition-plugin-section">
Expand Down
333 changes: 170 additions & 163 deletions docs/downloads/releases/renjs.js

Large diffs are not rendered by default.

13 changes: 12 additions & 1 deletion docs/examples-gallery.html
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ <h5 class="card-title mb-3">
</div><!--//card-body-->
</div><!--//card-->
</div><!--//col-->
<div class="col-12 col-lg-3 py-3">
<div class="col-12 col-lg-3 py-3">
<div class="card shadow-sm">
<div class="card-body d-flex flex-column text-center">
<h5 class="card-title mb-3">
Expand All @@ -211,6 +211,17 @@ <h5 class="card-title mb-3">
</div><!--//card-body-->
</div><!--//card-->
</div><!--//col-->
<div class="col-12 col-lg-3 py-3">
<div class="card shadow-sm">
<div class="card-body d-flex flex-column text-center">
<h5 class="card-title mb-3">
<span class="card-title-text">Story Log Plugin</span>
</h5>
<img class="card-img" src="examples/assets/gallery/storylog.png" alt="Card image">
<a class="card-link-mask" href="examples/storylog.html" target="_blank"></a>
</div><!--//card-body-->
</div><!--//card-->
</div><!--//col-->
</div><!--//row-->
</div><!--//container-->

Expand Down
1 change: 1 addition & 0 deletions docs/examples/Config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ skiptime: 50
autotime: 150
# If logChoices is true, previously chosen choices will be shown in a different color
logChoices: true
keepStoryLog: true
# Default waiting time for timeouts if not specified
timeout: 5000
# punctuation can add extra time when showing text in the message box, per special character
Expand Down
Binary file added docs/examples/assets/gallery/storylog.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/examples/assets/gui/arrowbtn.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/examples/assets/gui/logbg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/examples/assets/gui/logbtn.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/examples/assets/gui/returnbtn.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
150 changes: 150 additions & 0 deletions docs/examples/plugins/TextLog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@

class TextLog extends RenJS.Plugin {

log = null;

text = null;
maxLines = 11;

pageUpBtn = null;
pageDownBtn = null;

currentPage = 0;
finalPage = -1;
pages = []

onInit() {
//create log object
//create phaser group to hold all graphic elements
this.log = this.game.add.group();
this.log.visible = false;
//create invisible graphics layer with input enabled so when the log is shown, nothing else in the gui "works"
const bgLayer = this.game.add.graphics();
this.log.addChild(bgLayer)
bgLayer.beginFill(0xFFFFFF);
bgLayer.drawRect(0, 0, this.game.width, this.game.height);
bgLayer.endFill();
bgLayer.alpha = 0;
bgLayer.inputEnabled = true;

//create log background centered in the screen
const bg = this.game.add.sprite(this.game.world.centerX,20,"logBg");
bg.anchor.set(0.5,0);
this.log.addChild(bg);

//create return button, to close the log
const btnY = 415;
const returnbtn = this.game.add.button(this.game.world.centerX,btnY,"logReturnBtn",async () => {
//hide log with fade (but faster)
const fade = this.game.storyConfig.fadetime
this.game.storyConfig.fadetime = 150;
await this.game.screenEffects.transition.FADEOUT(this.log)
this.game.storyConfig.fadetime = fade;

this.log.visible = false;
this.pages = [];
this.finalPage = -1;
this.currentPage = 0;
this.game.unpause();
},this,1,0,1,0,this.log);
returnbtn.anchor.set(0.5);

//create arrow buttons
this.pageUpBtn = this.game.add.button(this.game.world.centerX+80,btnY,"arrowBtn",async () => {
if (this.currentPage>0){
this.setPage(this.currentPage-1);
}

},this,1,0,1,0,this.log);
this.pageUpBtn.anchor.set(0,0.5);
this.pageDownBtn = this.game.add.button(this.game.world.centerX-80,btnY,"arrowBtn",async () => {
if (this.finalPage==-1 || this.currentPage<this.finalPage){
this.setPage(this.currentPage+1);
}
},this,1,0,1,0,this.log);
this.pageDownBtn.anchor.set(0,0.5);
//flip arrow
this.pageDownBtn.scale.x = -1;

// create empty text object where the text will be displayed
const style = {...this.game.gui.hud.mBoxes.default.config.text.style};
style.wordWrapWidth = 640;
this.text = this.game.add.text(80, 50, "", style);
this.log.addChild(this.text);
//add new action for the text log button
this.game.gui.bindingActions['showTextLog'] = async ()=>{
this.game.world.bringToTop(this.log);
this.game.pause();
//update log text, so it will display the last messages first
this.setPage(0);

//show log with fade in (but faster!)
this.log.alpha = 0;
this.log.visible = true;
const fade = this.game.storyConfig.fadetime
this.game.storyConfig.fadetime = 150;
await this.game.screenEffects.transition.FADEIN(this.log)
this.game.storyConfig.fadetime = fade;
}

}

setPage(page){
this.currentPage = page;
if (this.pages.length <= this.currentPage){
if (this.currentPage == 0){
this.buildPage(0);
} else {
this.buildPage(this.pages[this.currentPage-1].offset);
}
}
//only show page down button if you're NOT on the last page
this.pageDownBtn.visible = this.currentPage!=this.finalPage;
//only show page up if you're NOT on the first page
this.pageUpBtn.visible = this.currentPage!=0;
//set page
const message = this.game.gui.setTextStyles(this.pages[this.currentPage].message,this.text);
this.text.text = message;
}

buildPage(offset){
var lines = 0;
let finalRawText = '';
// build the log "page", with a max amount of lines
for (var i = this.game.storyLog.length -1 - offset; i>=0; i--){
const entry = this.game.storyLog[i];
var message = entry.message;
if (entry.choice){
message = " - "+message;
}
if (entry.actor){
const name = this.game.managers.character.characters[entry.actor].config.displayName
//we'll show the character name in white
message = "(color:#ffffff)"+name +":(end) "+message;
}
//remove any style tag to calculate the wordwrap
const finalMessage = this.game.gui.setTextStyles(message,this.text);
const messageLines = this.text.precalculateWordWrap(finalMessage)
if (messageLines.length + lines > this.maxLines){
//stop adding lines
break;
}
lines+=messageLines.length;
// use the raw message text to build the final message, so it will keep the style tags
finalRawText = message + '\n' + finalRawText;
}
// if the index got to the end of the story log, it's the last page
if (i==-1){
this.finalPage = this.currentPage;
// when you get to the last page, complete the text with empty lines
for (var j=0;j<this.maxLines-lines;j++){
finalRawText = '\n' + finalRawText;
}
}

this.pages.push({message: finalRawText, offset: this.game.storyLog.length-1 - i});

}
}

RenJSGame.addPlugin('TextLog',TextLog)
Loading

0 comments on commit a39aece

Please sign in to comment.