Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
196 KB
Referenced Files
None
Subscribers
None
diff --git a/docs/ScriptAPI.md b/docs/ScriptAPI.md
index 2391218..8addc8d 100644
--- a/docs/ScriptAPI.md
+++ b/docs/ScriptAPI.md
@@ -1,726 +1,754 @@
Me and My Shadow Script API Reference
=====================================
(draft)
The script language is Lua 5.2 (later we may bump it to 5.3).
Always check `ScriptAPI.cpp` for the newest API changed unmentioned in this document.
How to edit script
==================
To edit the script of a block, right click the block and select "Scripting".
To edit the script of the level, right click the empty space of the level and select "Scripting".
Currently the scenery block doesn't support scripting.
Available event types of block:
Event type | Description
----------------------|--------------
"playerWalkOn" | Fired once when the player walks on. (For example this is used in fragile block.)
"playerIsOn" | Fired every frame when the player is on.
"playerLeave" | Fired once when the player leaves.
"onCreate" | Fired when object creates.
"onEnterFrame" | Fired every frame.
"onPlayerInteraction" | Fired when the player press DOWN key. Currently this event only fires when the block type is TYPE_SWITCH.
"onToggle" | Fired when the block receives "toggle" from a switch/button. NOTE: The switch/button itself will also receive this event. This is used in an old example found on my old computer.
"onSwitchOn" | Fired when the block receives "switch on" from a switch/button. NOTE: The switch/button itself will also receive this event. This is used in an old example found on my old computer.
"onSwitchOff" | Fired when the block receives "switch off" from a switch/button. NOTE: The switch/button itself will also receive this event. This is used in an old example found on my old computer.
NOTE: During the event execution the global variable `this` temporarily points to current block. (Ad-hoc workaround!)
When the event execution ends the global variable `this` is reset to its previous value.
The block event may return an integer value (default is 0) to alter the game logic:
Return value | Description
-------------|--------------
0 | Skip the default game logic for this event.
1 | Do the default game logic for this event.
Available event types of level:
Event type | Description
-----------|--------------
"onCreate" | Fired when the level is created or the game is reset. This happens after all the blocks are created and their `onCreate` is called.
"onSave" | Fired when the game is saved.
"onLoad" | Fired when the game is loaded.
For the newest lists of event types, see `init()` function in `Functions.cpp`.
NOTE: the following methods to specify scripts can be used:
* Specify scripts for each events in the block script editing dialog.
* Only specify `onCreate` script in the block script editing dialog,
and use `setEventHandler()` function in script to specify scripts for other events dynamically.
* Only specify `onCreate` script in the level script editing dialog,
and use `setEventHandler()` function in script to specify scripts for other events for level/blocks dynamically.
Script API reference
====================
The "block" library
-------------------
### Static functions:
* getBlockById(id)
Returns the first block with specified id. If not found, returns `nil`.
Example:
~~~lua
local b=block.getBlockById("1")
local x,y=b:getLocation()
print(x..","..y)
~~~
* getBlocksById(id)
Returns the list of all blocks with specified id.
Example:
~~~lua
local l=block.getBlocksById("1")
for i,b in ipairs(l) do
local x,y=b:getLocation()
print(x..","..y)
end
~~~
* removeAll()
Remove all blocks.
* addBlock(string,[x],[y],[w],[h])
Add a new block (optionally give it a new position and size) and return the newly created block.
The `string` is the text representation of a block which is used in `.map` file.
The new block can have scripts and the `onCreate` script will be executed immediately.
Example:
~~~lua
-- Assume we have moving blocks of id 1,
-- then this newly added switch can operate existing moving blocks.
block.addBlock([[
tile(Switch,0,0,50,50){
behaviour=toggle
id=1
script(onCreate){
script="print('Hello world from onCreate of dynamically added block')"
}
}]],250,300)
~~~
Another example:
~~~lua
local b=block.addBlock('tile(MovingBlock,0,0)')
b:setBaseLocation(
math.random()*level.getWidth(),
math.random()*level.getHeight())
local bx,by=b:getBaseLocation()
for i=1,10 do
b:addMovingPos({
math.random()*level.getWidth()-bx,
math.random()*level.getHeight()-by,
math.random()*80+40
})
end
b:addMovingPos({0,0,math.random()*80+40})
~~~
* addBlocks(string,[positions]) / addBlocks(string,offsetX,offsetY)
Add new blocks (optionally give them new positions and sizes) and return an array the newly created blocks.
The `string` is the text representation of blocks which is used in `.map` file.
In the first form,
If `string` contains only one block, then it will be created repeatedly using the specified positions.
If it contains more than one block, then each block will use corresponding position in `positions`.
The `positions` is an array of new positions whose entry is of format `{[x],[y],[w],[h]}`.
In the second form,
the `string` can contain one or more blocks,
and the position of each block will be offset by two numbers `offsetX` and `offsetY`.
The new blocks can have scripts and the `onCreate` script will be executed immediately.
### Member functions:
* isValid() -- check the object is valid (i.e. not deleted, etc.)
* moveTo(x,y)
Move the block to the new position, update the velocity of block according to the position changed.
Example:
~~~lua
local b=block.getBlockById("1")
local x,y=b:getLocation()
b:moveTo(x+1,y)
~~~
* getLocation()
Returns the position of the block.
Example: see the example for moveTo().
* setLocation(x,y)
Move the block to the new position without updating the velocity of block.
Example: omitted since it's almost the same as moveTo().
* getBaseLocation() / setBaseLocation(x,y)
Get or set the base position of the block. Mainly used for moving blocks.
* growTo(w,h)
Resize the block, update the velocity of block according to the size changed.
NOTE: I don't think the velocity need to be updated when resizing block, so don't use this function.
Example: omitted since it's almost the same as setSize().
* getSize()
Returns the size of the block.
Example:
~~~lua
local b=block.getBlockById("1")
local w,h=b:getSize()
print(w..","..h)
~~~
* setSize(w,h)
Resize the block without updating the velocity of block.
Example:
~~~lua
local b=block.getBlockById("1")
local w,h=b:getSize()
b:setSize(w+1,h)
~~~
* getBaseSize() / setBaseSize(x,y)
Get or set the base size of the block. Mainly used for moving blocks.
* getType()
Returns the type of the block (which is a string).
Example:
~~~lua
local b=block.getBlockById("1")
local s=b:getType()
print(s)
~~~
* changeThemeState(new_state)
Change the state of the block to new_state (which is a string).
Example:
~~~lua
local b=block.getBlockById("1")
b:changeThemeState("activated")
~~~
* setVisible(b)
Set the visibility the block.
NOTE: The default value is `true`. If set to `false` the block is hidden completely,
the animation is stopped, can't receive any event, can't execute any scripts (except for `onCreate`),
can't be used as a portal destination,
doesn't participate in collision check and game logic, etc...
NOTE: This is a newly added feature.
If you find any bugs (e.g. if an invisible block still affects the game logic)
please report the bugs to GitHub issue tracker.
Example:
~~~lua
local b=block.getBlockById("1")
if b:isVisible() then
b:setVisible(false)
else
b:setVisible(true)
end
~~~
* isVisible()
Returns whether the block is visible.
Example: see the example for setVisible().
* getEventHandler(event_type)
Returns the event handler of event_type (which is a string).
Example:
~~~lua
local b=block.getBlockById("1")
local f=b:getEventHandler("onSwitchOn")
b:setEventHandler("onSwitchOff",f)
~~~
* setEventHandler(event_type,handler)
Set the handler of event_type (which is a string). The handler should be a function or `nil`.
Returns the previous event handler.
Example:
~~~lua
local b=block.getBlockById("1")
b:setEventHandler("onSwitchOff",function()
print("I am switched off.")
end)
~~~
* onEvent(eventType)
Fire an event to specified block.
NOTE: The event will be processed immediately.
Example:
~~~lua
local b=block.getBlockById("1")
b:onEvent("onToggle")
~~~
NOTE: Be careful not to write infinite recursive code! Bad example:
~~~lua
-- onToggle event of a moving block
this:onEvent("onToggle")
~~~
* isActivated() / setActivated(bool)
Get/set a boolean indicates if the block is activated.
The block should be one of TYPE_MOVING_BLOCK, TYPE_MOVING_SHADOW_BLOCK, TYPE_MOVING_SPIKES,
TYPE_CONVEYOR_BELT, TYPE_SHADOW_CONVEYOR_BELT.
* isAutomatic() / setAutomatic(bool)
Get/set a boolean indicates if the portal is automatic.
The block should be TYPE_PORTAL.
* getBehavior() / setBehavior(str)
Get/set a string (must be "on", "off" or "toggle"),
representing the behavior of the block.
The block should be TYPE_BUTTON, TYPE_SWITCH.
* getState() / setState(num)
Get/set a number (must be 0,1,2 or 3),
representing the state of a fragile block.
The block should be TYPE_FRAGILE.
* isPlayerOn()
Get a boolean indicates if the player is on.
Currently only works for TYPE_BUTTON.
* getPathMaxTime()
Get the total time of the path of a moving block.
* getPathTime() / setPathTime(num)
Get/set the current time of the path of a moving block.
* isLooping() / setLooping(bool)
Get/set the looping property of a moving block.
* getSpeed() / setSpeed(num)
Get/set the speed of a conveyor belt.
NOTE: 1 Speed = 0.08 block/s = 0.1 pixel/frame.
* getAppearance() / setAppearance(str)
Get/set the custom appearance of a block.
The `str` is the name of the custom appearance, either `"<blockName>_Scenery"` or name of a scenery block.
Empty string or nil means the default appearance.
* getId() / setId(str)
Get/set the id of a block.
* getDestination() / setDestination(str)
Get/set the destination of a portal.
The block should be TYPE_PORTAL.
* getMessage() / setMessage(str)
Get/set the message of a notify block.
The block should be TYPE_NOTIFICATION_BLOCK.
* getMovingPosCount()
Get the number of moving positions of a moving block.
* getMovingPos() / getMovingPos(index) / getMovingPos(start, length)
Get the array of moving positions or the moving position at specified index
(the array index starts with 1 in Lua).
The individual point is of format `{x,y,t}`.
* setMovingPos(array) / setMovingPos(index, point) / setMovingPos(start, length, array)
Set the array of moving positions or modify the moving position at specified index
(the array index starts with 1 in Lua).
NOTE: the last two forms won't change the number of points,
while the first form will overwrite the list of points completely.
* addMovingPos(p) / addMovingPos(index, p)
Insert points to the array of moving positions at the end or at the specified index.
The `p` can be one point or a list of points.
* removeMovingPos() / removeMovingPos(index) / removeMovingPos(listOfIndices) / removeMovingPos(start, length)
Remove points in the array of moving positions: remove all points,
or remove a point at specified index, or remove points at specified indices,
or remove points in given range.
* clone([x],[y],[w],[h])
Create a clone of current block, optionally give it a new position and size.
The new block can have scripts and the `onCreate` script will be executed immediately.
Returns the newly created block.
* cloneMultiple(number) / cloneMultiple(positions)
Create multiple clones of current block, optionally give them new positions and sizes.
The `number` is the number of clones to made, whose positions are the same as the source block,
whereas `positions` is an array of new positions whose entry is of format `{[x],[y],[w],[h]}`.
The new blocks can have scripts and the `onCreate` script will be executed immediately.
Returns an array of newly created blocks.
* remove()
Remove current block.
The "playershadow" library
--------------------------
### Global constants:
* player
The player object.
* shadow
The shadow object.
### Member functions:
* getLocation()
Returns the location of player/shadow.
Example:
~~~lua
local x,y=player:getLocation()
print("player: "..x..","..y)
x,y=shadow:getLocation()
print("shadow: "..x..","..y)
~~~
* setLocation(x,y)
Set the location of player/shadow.
Example:
~~~lua
local x,y=player:getLocation()
player:setLocation(x+1,y)
~~~
* jump([strength=13])
Let the player/shadow jump if it's allowed.
strength: Jump strength.
Example:
~~~lua
player:jump(20)
~~~
* isShadow()
Returns whether the current object is shadow.
Example:
~~~lua
print(player:isShadow())
print(shadow:isShadow())
~~~
* getCurrentStand()
Returns the block on which the player/shadow is standing on. Can be `nil`.
Example:
~~~lua
local b=player:getCurrentStand()
if b then
print(b:getType())
else
print("The player is not standing on any blocks")
end
~~~
* isInAir() -- returns a boolean indicating if the player is in air
* canMove() -- returns a boolean indicating if the player can move (i.e. not standing on shadow)
* isDead() -- returns a boolean indicating if the player is dead
* isHoldingOther() -- returns a boolean indicating if the player is holding other
The "level" library
-------------------
### Static functions:
* getSize() -- get the level size
* getRect() / setRect(x,y,w,h) -- get or set the level rect (left,top,width,height)
* getWidth() -- get the level width
* getHeight() -- get the level height
* getName() -- get the level name
* getEventHandler(event_type) -- get the event handler
* setEventHandler(event_type,handler) -- set the event handler, return the old handler
* win() -- win the game
* getTime() -- get the game time (in frames)
* getRecordings() -- get the game recordings
* broadcastObjectEvent(eventType,[objectType=nil],[id=nil],[target=nil])
Broadcast the event to blocks satisfying the specified condition.
NOTE: The event will be processed in next frame.
Argument name | Description
--------------|-------------
eventType | string.
objectType | string or nil. If this is set then the event is only received by the block with specified type.
id | string or nil. If this is set then the event is only received by the block with specified id.
target | block or nil. If this is set then the event is only received by the specified block.
Example:
~~~lua
level.broadcastObjectEvent("onToggle",nil,"1")
~~~
The "delayExecution" library
----------------------------
### Static functions:
* schedule(func,time,[repeatCount=1],[repeatInterval],[enabled=true],[arguments...])
Schedule a delay execution of a given function after the given time.
Argument name | Description
---------------|-------------
func | A function to be executed.
time | Time, given in frames (NOTE: 40 frames = 1 second). NOTE: If <=0 it is the same as =1.
repeatCount | The number of times the function will be executed. After such number of times executed, the delay execution will be removed from the list and get deleted. If =0 the delay execution object will be deleted soon. If <0 the function will be executed indefinitely.
repeatInterval | The repeat interval. If it is `nil` then the `time` argument will be used instead. NOTE: If <=0 the repeat execution will be disabled at all and the repeatCount will be set to 1.
enabled | Enabled.
arguments | Optional arguments passed to the function.
Return value: the delayExecution object.
NOTE: If you want to update time/repeatCount during the function execution,
notice that the time/repeatCount is updated BEFORE the function execution.
NOTE: During the execution the global variable `this`
temporarily points to current delay execution object. (Ad-hoc workaround!)
When the execution ends the global variable `this` is reset to its previous value.
Example:
~~~lua
local f=function()
local a
a=0
return(function(b)
shadow:jump()
print('obj1 '..this:getExecutionTime()..' '..a..' '..tostring(b))
a=a+2
end)
end
local obj1=delayExecution.schedule(f(),40*2,5,nil,nil,100)
local obj2=delayExecution.schedule(
function(o)
print('obj2 '..tostring(o:isValid()))
if not o:isValid() then
this:setFunc(f())
end
end,40*1,-1,nil,nil,obj1)
local obj3=delayExecution.schedule(
function(o)
o:cancel()
end,40*30,1,nil,nil,obj2)
~~~
### Member functions:
* isValid() -- Check if it's valid, i.e. not removed from list.
* cancel() -- Cancels a delay execution. The canceled delay execution will be removed from the list and can not be restored.
* isEnabled()/setEnabled(bool) -- get/set enabled of a delay execution. A disabled one will not count down its timer.
* getTime()/setTime(integer) -- get/set the remaining time until the next execution. NOTE: If <=0 it is the same as =1.
* getRepeatCount()/setRepeatCount(integer) -- get/set the remaining repeat count. If =0 the object will get deleted soon. If <0 the function will be executed indefinitely.
* getRepeatInterval()/setRepeatInterval(integer) -- get/set the repeat interval. NOTE: If <=0 then nothing happens.
* getFunc()/setFunc(func) -- get/set the function to be executed. NOTE: The setFunc will return the original function.
* getArguments()/setArguments(args...) -- get/set the arguments
* getExecutionTime()/setExecutionTime(integer) -- get/set the number of times the function being executed. NOTE: this execution time doesn't affect the default logic.
The "camera" library
--------------------
### Static functions:
* setMode(mode) -- set the camera mode, which is "player" or "shadow"
* lookAt(x,y) -- set the camera mode to "custom" and set the new center of camera
The "audio" library
-------------------
NOTE: the following functions are not going to work if the sound/music volume is 0.
### Static functions:
* playSound(name[,concurrent=-1[,force=false[,fade=-1]]])
Play a sound effect.
Argument name | Description
--------------|-------------
name | The name of the sound effect. Currently available: "jump", "hit", "checkpoint", "swap", "toggle", "error", "collect", "achievement".
concurrent | The number of times the same sfx can be played at once, -1 is unlimited. NOTE: there are 64 channels.
force | If the sound must be played even if all channels are used. In this case the sound effect in the first channel will be stopped.
fade | A factor to temporarily turn the music volume down (0-128). -1 means don't use this feature.
Return value: The channel of the sfx. -1 means failed (channel is full, invalid sfx name, sfx volume is 0, etc.)
* playMusic(name[,fade=true])
Play a music.
Argument name | Description
--------------|-------------
name | The name of the song, e.g. "default/neverending" or "menu".
fade | Boolean if it should fade the current one out or not.
* pickMusic() - pick a song from the current music list.
* getMusicList()/setMusicList(name_of_the_music_list) - get/set the music list. Example: "default".
* currentMusic() - get the current music.
The "gettext" library
--------------------
This library is used for translation support.
NOTE: Currently this library only uses the dictionary for current level pack.
This means it doesn't work for individual level which doesn't contain in a level pack,
and it can't make use of the translations of the core game.
### Global functions:
* `_(msgid)` -- translate the string using default context
* `__(msgid)` -- does nothing, just outputs the original string. However, it will be scanned by `xgettext`.
Mainly used in block:setMessage() since the block message will always be passed to gettext().
Also used in construction of an array of strings, which will be translated dynamically.
### Static functions:
* gettext(msgid) -- translate the string using default context
* pgettext(msgctxt,msgid) -- translate the string using specified context
* ngettext(msgid,msgid_plural,n) -- translate the string using default context, taking plural form into consideration
* npgettext(msgctxt,msgid,msgid_plural,n) -- translate the string using specified context, taking plural form into consideration
+
+The "prng" library
+--------------------
+
+The Mersenne Twister 19937 pseudo-random number generator.
+
+The random seed is recreated each time the game starts, and is saved to the record file,
+which ensures the reproducibility of the replay.
+
+### Static functions:
+
+* random() / random(n) / random(m,n)
+
+These functions have the same arguments as the Lua built-in function `math.random()`. More precisely:
+
+When called without arguments, returns a pseudo-random float with uniform distribution in the range `[0,1)`.
+
+When called with two integers `m` and `n`, returns a pseudo-random integer with uniform distribution in the range `[m, n]`.
+The `m` cannot be larger than `n`
+(othewise it will return a pseudo-random integer with uniform distribution in the union of `[m, 2^63-1]` and `[-2^63, n]`)
+and must fit in two Lua integers.
+
+The call `random(n)` is equivalent to `random(1,n)`.
+
+* getSeed() / setSeed(string)
+
+Get or set the random seed, which is a string.
+This is mainly used when you want the pseudo-random number to be reproducible even between each plays.
diff --git a/src/Game.cpp b/src/Game.cpp
index f921455..6ff589a 100644
--- a/src/Game.cpp
+++ b/src/Game.cpp
@@ -1,1901 +1,1963 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Block.h"
#include "GameState.h"
#include "Functions.h"
#include "GameObjects.h"
#include "ThemeManager.h"
#include "Game.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "InputManager.h"
#include "MusicManager.h"
#include "Render.h"
#include "StatisticsManager.h"
#include "ScriptExecutor.h"
+#include "MD5.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <vector>
#include <map>
#include <algorithm>
#include <assert.h>
#include <stdio.h>
#include <SDL_ttf.h>
#include "libs/tinyformat/tinyformat.h"
using namespace std;
const char* Game::blockName[TYPE_MAX]={"Block","PlayerStart","ShadowStart",
"Exit","ShadowBlock","Spikes",
"Checkpoint","Swap","Fragile",
"MovingBlock","MovingShadowBlock","MovingSpikes",
"Teleporter","Button","Switch",
"ConveyorBelt","ShadowConveyorBelt","NotificationBlock", "Collectable", "Pushable"
};
map<string,int> Game::blockNameMap;
map<int,string> Game::gameObjectEventTypeMap;
map<string,int> Game::gameObjectEventNameMap;
map<int,string> Game::levelEventTypeMap;
map<string,int> Game::levelEventNameMap;
string Game::recordFile;
//An internal function.
static void copyCompiledScripts(lua_State *state, const std::map<int, int>& src, std::map<int, int>& dest) {
//Clear the existing scripts.
for (auto it = dest.begin(); it != dest.end(); ++it) {
luaL_unref(state, LUA_REGISTRYINDEX, it->second);
}
dest.clear();
//Copy the source to the destination.
for (auto it = src.begin(); it != src.end(); ++it) {
lua_rawgeti(state, LUA_REGISTRYINDEX, it->second);
dest[it->first] = luaL_ref(state, LUA_REGISTRYINDEX);
}
}
//An internal function.
static void copyLevelObjects(const std::vector<Block*>& src, std::vector<Block*>& dest, bool setActive) {
//Clear the existing objects.
for (auto o : dest) delete o;
dest.clear();
//Copy the source to the destination.
for (auto o : src) {
if (o == NULL || o->isDelete) continue;
Block* o2 = new Block(*o);
dest.push_back(o2);
if (setActive) o2->setActive();
}
}
Game::Game(SDL_Renderer &renderer, ImageManager &imageManager):isReset(false)
, scriptExecutor(new ScriptExecutor())
,currentLevelNode(NULL)
,customTheme(NULL)
,background(NULL)
, levelRect(SDL_Rect{ 0, 0, 0, 0 }), levelRectSaved(SDL_Rect{ 0, 0, 0, 0 }), levelRectInitial(SDL_Rect{ 0, 0, 0, 0 })
,won(false)
,interlevel(false)
,gameTipIndex(0)
,time(0),timeSaved(0)
,recordings(0),recordingsSaved(0)
,cameraMode(CAMERA_PLAYER),cameraModeSaved(CAMERA_PLAYER)
,player(this),shadow(this),objLastCheckPoint(NULL)
, currentCollectables(0), currentCollectablesSaved(0), currentCollectablesInitial(0)
, totalCollectables(0), totalCollectablesSaved(0), totalCollectablesInitial(0)
{
saveStateNextTime=false;
loadStateNextTime=false;
recentSwap=recentSwapSaved=-10000;
recentLoad=recentSave=0;
action=imageManager.loadTexture(getDataPath()+"gfx/actions.png", renderer);
medals=imageManager.loadTexture(getDataPath()+"gfx/medals.png", renderer);
//Get the collectable image from the theme.
//NOTE: Isn't there a better way to retrieve the image?
objThemes.getBlock(TYPE_COLLECTABLE)->createInstance(&collectable);
//Hide the cursor if not in the leveleditor.
if(stateID!=STATE_LEVEL_EDITOR)
SDL_ShowCursor(SDL_DISABLE);
}
Game::~Game(){
//Simply call our destroy method.
destroy();
//Before we leave make sure the cursor is visible.
SDL_ShowCursor(SDL_ENABLE);
}
void Game::destroy(){
delete scriptExecutor;
scriptExecutor = NULL;
//Loop through the levelObjects, etc. and delete them.
for (auto o : levelObjects) delete o;
levelObjects.clear();
for (auto o : levelObjectsSave) delete o;
levelObjectsSave.clear();
for (auto o : levelObjectsInitial) delete o;
levelObjectsInitial.clear();
//Loop through the sceneryLayers and delete them.
for(auto it=sceneryLayers.begin();it!=sceneryLayers.end();++it){
delete it->second;
}
sceneryLayers.clear();
//Clear the name and the editor data.
levelName.clear();
levelFile.clear();
editorData.clear();
//Remove everything from the themeManager.
background=NULL;
if(customTheme)
objThemes.removeTheme();
customTheme=NULL;
//If there's a (partial) theme bundled with the levelpack remove that as well.
if(levels->customTheme)
objThemes.removeTheme();
//delete current level (if any)
if(currentLevelNode){
delete currentLevelNode;
currentLevelNode=NULL;
}
//Reset the time.
time=timeSaved=0;
recordings=recordingsSaved=0;
recentSwap=recentSwapSaved=-10000;
//Set the music list back to the configured list.
getMusicManager()->setMusicList(getSettings()->getValue("musiclist"));
}
void Game::reloadMusic() {
//NOTE: level music is always enabled.
//Check if the levelpack has a prefered music list.
if (levels && !levels->levelpackMusicList.empty())
getMusicManager()->setMusicList(levels->levelpackMusicList);
//Check for the music to use.
string &s = editorData["music"];
if (!s.empty()) {
getMusicManager()->playMusic(s);
} else {
getMusicManager()->pickMusic();
}
}
void Game::loadLevelFromNode(ImageManager& imageManager,SDL_Renderer& renderer,TreeStorageNode* obj,const string& fileName){
//Make sure there's nothing left from any previous levels.
assert(levelObjects.empty() && levelObjectsSave.empty() && levelObjectsInitial.empty());
//set current level to loaded one.
currentLevelNode=obj;
//Set the level dimensions to the default, it will probably be changed by the editorData,
//but 800x600 is a fallback.
levelRect = levelRectSaved = levelRectInitial = SDL_Rect{ 0, 0, 800, 600 };
currentCollectables = currentCollectablesSaved = currentCollectablesInitial = 0;
totalCollectables = totalCollectablesSaved = totalCollectablesInitial = 0;
//Load the additional data.
for(map<string,vector<string> >::iterator i=obj->attributes.begin();i!=obj->attributes.end();++i){
if(i->first=="size"){
//We found the size attribute.
if(i->second.size()>=2){
//Set the dimensions of the level.
int w = atoi(i->second[0].c_str()), h = atoi(i->second[1].c_str());
levelRect = levelRectSaved = levelRectInitial = SDL_Rect{ 0, 0, w, h };
}
}else if(i->second.size()>0){
//Any other data will be put into the editorData.
editorData[i->first]=i->second[0];
}
}
//Get the theme.
{
//NOTE: level themes are always enabled.
//Check for bundled (partial) themes for level pack.
if (levels->customTheme){
if (objThemes.appendThemeFromFile(levels->levelpackPath + "/theme/theme.mnmstheme", imageManager, renderer) == NULL){
//The theme failed to load so set the customTheme boolean to false.
levels->customTheme = false;
}
}
//Check for the theme to use for this level. This has higher priority.
//Examples: %DATA%/themes/classic or %USER%/themes/Orange
string &s = editorData["theme"];
if (!s.empty()){
customTheme = objThemes.appendThemeFromFile(processFileName(s) + "/theme.mnmstheme", imageManager, renderer);
}
//Set the Appearance of the player and the shadow.
objThemes.getCharacter(false)->createInstance(&player.appearance, "standright");
player.appearanceInitial = player.appearanceSave = player.appearance;
objThemes.getCharacter(true)->createInstance(&shadow.appearance, "standright");
shadow.appearanceInitial = shadow.appearanceSave = shadow.appearance;
}
//Get the music.
reloadMusic();
//Load the data from the level node.
for(unsigned int i=0;i<obj->subNodes.size();i++){
TreeStorageNode* obj1=obj->subNodes[i];
if(obj1==NULL) continue;
if(obj1->name=="tile"){
Block* block=new Block(this);
if(!block->loadFromNode(imageManager,renderer,obj1)){
delete block;
continue;
}
//If the type is collectable, increase the number of totalCollectables
if (block->type == TYPE_COLLECTABLE) {
totalCollectablesSaved = totalCollectablesInitial = ++totalCollectables;
}
//Add the block to the levelObjects vector.
levelObjects.push_back(block);
}else if(obj1->name=="scenerylayer" && obj1->value.size()==1){
//Check if the layer exists.
if (sceneryLayers[obj1->value[0]] == NULL) {
sceneryLayers[obj1->value[0]] = new SceneryLayer();
}
//Load contents from node.
sceneryLayers[obj1->value[0]]->loadFromNode(this, imageManager, renderer, obj1);
}else if(obj1->name=="script" && !obj1->value.empty()){
map<string,int>::iterator it=Game::levelEventNameMap.find(obj1->value[0]);
if(it!=Game::levelEventNameMap.end()){
int eventType=it->second;
const std::string& script=obj1->attributes["script"][0];
if(!script.empty()) scripts[eventType]=script;
}
}
}
//Set the levelName to the name of the current level.
levelName=editorData["name"];
levelFile=fileName;
//Some extra stuff only needed when not in the levelEditor.
if(stateID!=STATE_LEVEL_EDITOR){
//We create a text with the text "Level <levelno> <levelName>".
//It will be shown in the left bottom corner of the screen.
string s;
if(levels->getLevelCount()>1 && levels->type!=COLLECTION){
s=tfm::format(_("Level %d %s"),levels->getCurrentLevel()+1,_CC(levels->getDictionaryManager(),editorData["name"]));
} else {
s = _CC(levels->getDictionaryManager(), editorData["name"]);
}
bmTips[0]=textureFromText(renderer, *fontText,s.c_str(),objThemes.getTextColor(true));
}
//Get the background
background=objThemes.getBackground(false);
//Now the loading is finished, we reset all objects to their initial states.
//Before doing that we swap the levelObjects to levelObjectsInitial.
std::swap(levelObjects, levelObjectsInitial);
reset(true, stateID == STATE_LEVEL_EDITOR);
}
void Game::loadLevel(ImageManager& imageManager,SDL_Renderer& renderer,std::string fileName){
//Create a TreeStorageNode that will hold the loaded data.
TreeStorageNode *obj=new TreeStorageNode();
{
POASerializer objSerializer;
string s=fileName;
//Parse the file.
if(!objSerializer.loadNodeFromFile(s.c_str(),obj,true)){
cerr<<"ERROR: Can't load level file "<<s<<endl;
delete obj;
return;
}
}
//Now call another function.
loadLevelFromNode(imageManager,renderer,obj,fileName);
}
void Game::saveRecord(const char* fileName){
//check if current level is NULL (which should be impossible)
if(currentLevelNode==NULL) return;
TreeStorageNode obj;
POASerializer objSerializer;
//put current level to the node.
currentLevelNode->name="map";
obj.subNodes.push_back(currentLevelNode);
+ //put the random seed into the attributes.
+ obj.attributes["seed"].push_back(prngSeed);
+
//serialize the game record using RLE compression.
#define PUSH_BACK \
if(j>0){ \
if(j>1){ \
sprintf(c,"%d*%d",last,j); \
}else{ \
sprintf(c,"%d",last); \
} \
v.push_back(c); \
}
vector<string> &v=obj.attributes["record"];
vector<int> *record=player.getRecord();
char c[64];
int i,j=0,last;
for(i=0;i<(int)record->size();i++){
int currentKey=(*record)[i];
if(j==0 || currentKey!=last){
PUSH_BACK;
last=currentKey;
j=1;
}else{
j++;
}
}
PUSH_BACK;
#undef PUSH_BACK
#ifdef RECORD_FILE_DEBUG
//add record file debug data.
{
obj.attributes["recordKeyPressLog"].push_back(player.keyPressLog());
vector<SDL_Rect> &playerPosition=player.playerPosition();
string s;
char c[32];
sprintf(c,"%d\n",int(playerPosition.size()));
s=c;
for(unsigned int i=0;i<playerPosition.size();i++){
SDL_Rect& r=playerPosition[i];
sprintf(c,"%d %d\n",r.x,r.y);
s+=c;
}
obj.attributes["recordPlayerPosition"].push_back(s);
}
#endif
//save it
objSerializer.saveNodeToFile(fileName,&obj,true,true);
//remove current level from node to prevent delete it.
obj.subNodes.clear();
}
void Game::loadRecord(ImageManager& imageManager, SDL_Renderer& renderer, const char* fileName){
//Create a TreeStorageNode that will hold the loaded data.
TreeStorageNode obj;
{
POASerializer objSerializer;
- string s=fileName;
//Parse the file.
- if(!objSerializer.loadNodeFromFile(s.c_str(),&obj,true)){
- cerr<<"ERROR: Can't load record file "<<s<<endl;
+ if(!objSerializer.loadNodeFromFile(fileName,&obj,true)){
+ cerr<<"ERROR: Can't load record file "<<fileName<<endl;
return;
}
}
+ //Load the seed of psuedo-random number generator.
+ prngSeed.clear();
+ {
+ auto it = obj.attributes.find("seed");
+ if (it != obj.attributes.end() && !it->second.empty()) {
+ prngSeed = it->second[0];
+ }
+ }
+
//find the node named 'map'.
bool loaded=false;
for(unsigned int i=0;i<obj.subNodes.size();i++){
if(obj.subNodes[i]->name=="map"){
//load the level. (fileName=???)
- loadLevelFromNode(imageManager,renderer,obj.subNodes[i],"???");
+ loadLevelFromNode(imageManager,renderer,obj.subNodes[i],"?record?");
//remove this node to prevent delete it.
obj.subNodes[i]=NULL;
//over
loaded=true;
break;
}
}
if(!loaded){
cerr<<"ERROR: Can't find subnode named 'map' from record file"<<endl;
return;
}
//load the record.
{
vector<int> *record=player.getRecord();
record->clear();
vector<string> &v=obj.attributes["record"];
for(unsigned int i=0;i<v.size();i++){
string &s=v[i];
string::size_type pos=s.find_first_of('*');
if(pos==string::npos){
//1 item only.
int i=atoi(s.c_str());
record->push_back(i);
}else{
//contains many items.
int i=atoi(s.substr(0,pos).c_str());
int j=atoi(s.substr(pos+1).c_str());
for(;j>0;j--){
record->push_back(i);
}
}
}
}
#ifdef RECORD_FILE_DEBUG
//load the debug data
{
vector<string> &v=obj.attributes["recordPlayerPosition"];
vector<SDL_Rect> &playerPosition=player.playerPosition();
playerPosition.clear();
if(!v.empty()){
if(!v[0].empty()){
stringstream st(v[0]);
int m;
st>>m;
for(int i=0;i<m;i++){
SDL_Rect r;
st>>r.x>>r.y;
r.w=0;
r.h=0;
playerPosition.push_back(r);
}
}
}
}
#endif
//play the record.
//TODO: tell the level manager don't save the level progress.
player.playRecord();
shadow.playRecord(); //???
}
/////////////EVENT///////////////
void Game::handleEvents(ImageManager& imageManager, SDL_Renderer& renderer){
//First of all let the player handle input.
player.handleInput(&shadow);
//Check for an SDL_QUIT event.
if(event.type==SDL_QUIT){
//We need to quit so enter STATE_EXIT.
setNextState(STATE_EXIT);
}
//Check for the escape key.
if(stateID != STATE_LEVEL_EDITOR && inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
//Escape means we go one level up, to the level select state.
setNextState(STATE_LEVEL_SELECT);
//Save the progress.
levels->saveLevelProgress();
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}
//Check if 'R' is pressed.
if(inputMgr.isKeyDownEvent(INPUTMGR_RESTART)){
//Restart game only if we are not watching a replay.
if (!player.isPlayFromRecord() || interlevel) {
//Reset the game at next frame.
isReset = true;
//Also delete any gui (most likely the interlevel gui). Only in game mode.
if (GUIObjectRoot && stateID != STATE_LEVEL_EDITOR){
delete GUIObjectRoot;
GUIObjectRoot = NULL;
}
//And set interlevel to false.
interlevel = false;
}
}
//Check for the next level buttons when in the interlevel popup.
if (inputMgr.isKeyDownEvent(INPUTMGR_SPACE) || inputMgr.isKeyDownEvent(INPUTMGR_SELECT)){
if(interlevel){
//The interlevel popup is shown so we need to delete it.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Now goto the next level.
gotoNextLevel(imageManager,renderer);
}
}
//Check if tab is pressed.
if(inputMgr.isKeyDownEvent(INPUTMGR_TAB)){
//Switch the camera mode.
switch(cameraMode){
case CAMERA_PLAYER:
cameraMode=CAMERA_SHADOW;
break;
case CAMERA_SHADOW:
case CAMERA_CUSTOM:
cameraMode=CAMERA_PLAYER;
break;
}
}
}
/////////////////LOGIC///////////////////
void Game::logic(ImageManager& imageManager, SDL_Renderer& renderer){
//Add one tick to the time.
time++;
//NOTE: This code reverts some changes in commit 5f03ae5.
//This is part of old prepareFrame() code.
//This is needed since otherwise the script function block:setLocation() and block:moveTo() are completely broken.
//Later we should rewrite collision system completely which will remove this piece of code.
//NOTE: In new collision system the effect of dx/dy/xVel/yVel should only be restricted in one frame.
for (auto obj : levelObjects) {
switch (obj->type) {
default:
obj->dx = obj->dy = obj->xVel = obj->yVel = 0;
break;
case TYPE_PUSHABLE:
//NOTE: Currently the dx/dy/etc. of pushable blocks are still carry across frames, in order to make the collision system work correct.
break;
case TYPE_CONVEYOR_BELT: case TYPE_SHADOW_CONVEYOR_BELT:
//NOTE: We let the conveyor belt itself to reset its xVel/yVel.
obj->dx = obj->dy = 0;
break;
}
}
//NOTE2: The above code breaks pushable block with moving block in most cases,
//more precisely, if the pushable block is processed before the moving block then things may be broken.
//Therefore later we must process other blocks before moving pushable block.
//Process delay execution scripts.
getScriptExecutor()->processDelayExecution();
//Process any event in the queue.
for(unsigned int idx=0;idx<eventQueue.size();idx++){
//Get the event from the queue.
typeGameObjectEvent &e=eventQueue[idx];
//Check if the it has an id attached to it.
if(e.target){
//NOTE: Should we check if the target still exists???
e.target->onEvent(e.eventType);
}else if(e.flags&1){
//Loop through the levelObjects and give them the event if they have the right id.
for(unsigned int i=0;i<levelObjects.size();i++){
if(e.objectType<0 || levelObjects[i]->type==e.objectType){
if(levelObjects[i]->id==e.id){
levelObjects[i]->onEvent(e.eventType);
}
}
}
}else{
//Loop through the levelObjects and give them the event.
for(unsigned int i=0;i<levelObjects.size();i++){
if(e.objectType<0 || levelObjects[i]->type==e.objectType){
levelObjects[i]->onEvent(e.eventType);
}
}
}
}
//Done processing the events so clear the queue.
eventQueue.clear();
//Remove levelObjects whose isDelete is true.
{
int j = 0;
for (int i = 0; i < (int)levelObjects.size(); i++) {
if (levelObjects[i] == NULL) {
j++;
} else if (levelObjects[i]->isDelete) {
delete levelObjects[i];
levelObjects[i] = NULL;
j++;
} else if (j > 0) {
levelObjects[i - j] = levelObjects[i];
}
}
if (j > 0) levelObjects.resize(levelObjects.size() - j);
}
//Check if we should save/load state.
//NOTE: This happens after event handling so no eventQueue has to be saved/restored.
if(saveStateNextTime){
saveState();
}else if(loadStateNextTime){
loadState();
}
saveStateNextTime=false;
loadStateNextTime=false;
//Loop through the gameobjects to update them.
for(unsigned int i=0;i<levelObjects.size();i++){
//Send GameObjectEvent_OnEnterFrame event to the script
levelObjects[i]->onEvent(GameObjectEvent_OnEnterFrame);
}
//Let the gameobject handle movement.
{
std::vector<Block*> pushableBlocks;
//First we process blocks which are not pushable blocks.
for (auto o : levelObjects) {
if (o->type == TYPE_PUSHABLE) {
pushableBlocks.push_back(o);
} else {
o->move();
}
}
//Sort pushable blocks by their position, which is an ad-hoc workaround for
//<https://forum.freegamedev.net/viewtopic.php?f=48&t=8047#p77692>.
std::stable_sort(pushableBlocks.begin(), pushableBlocks.end(),
[](const Block* obj1, const Block* obj2)->bool
{
SDL_Rect r1 = const_cast<Block*>(obj1)->getBox(), r2 = const_cast<Block*>(obj2)->getBox();
if (r1.y > r2.y) return true;
else if (r1.y < r2.y) return false;
else return r1.x < r2.x;
});
//Now we process pushable blocks.
for (auto o : pushableBlocks) {
o->move();
}
}
//Also update the scenery.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it){
it->second->updateAnimation();
}
//Let the player store his move, if recording.
player.shadowSetState();
//Let the player give his recording to the shadow, if configured.
player.shadowGiveState(&shadow);
//NOTE: to fix bugs regarding player/shadow swap, we should first process collision of player/shadow then move them
const SDL_Rect playerLastPosition = player.getBox();
const SDL_Rect shadowLastPosition = shadow.getBox();
//NOTE: The following is ad-hoc code to fix shadow on blocked player on conveyor belt bug
if (shadow.holdingOther) {
//We need to process shadow collision first if shadow is holding player.
//Let the shadow decide his move, if he's playing a recording.
shadow.moveLogic();
//Check collision for shadow.
shadow.collision(levelObjects, NULL);
//Get the new position of it.
const SDL_Rect r = shadow.getBox();
//Check collision for player. Transfer the velocity of shadow to it only if the shadow moves its position.
player.collision(levelObjects, (r.x != shadowLastPosition.x || r.y != shadowLastPosition.y) ? &shadow : NULL);
} else {
//Otherwise we process player first.
//Check collision for player.
player.collision(levelObjects, NULL);
//Get the new position of it.
const SDL_Rect r = player.getBox();
//Now let the shadow decide his move, if he's playing a recording.
shadow.moveLogic();
//Check collision for shadow. Transfer the velocity of player to it only if the player moves its position.
shadow.collision(levelObjects, (r.x != playerLastPosition.x || r.y != playerLastPosition.y) ? &player : NULL);
}
//Let the player move.
player.move(levelObjects, playerLastPosition.x, playerLastPosition.y);
//Let the shadow move.
shadow.move(levelObjects, shadowLastPosition.x, shadowLastPosition.y);
//Check collision and stuff for the shadow and player.
player.otherCheck(&shadow);
//Update the camera.
switch(cameraMode){
case CAMERA_PLAYER:
player.setMyCamera();
break;
case CAMERA_SHADOW:
shadow.setMyCamera();
break;
case CAMERA_CUSTOM:
//NOTE: The target is (should be) screen size independent so calculate the real target x and y here.
int targetX=cameraTarget.x-(SCREEN_WIDTH/2);
int targetY=cameraTarget.y-(SCREEN_HEIGHT/2);
//Move the camera to the cameraTarget.
if(camera.x>targetX){
camera.x-=(camera.x-targetX)>>4;
//Make sure we don't go too far.
if(camera.x<targetX)
camera.x=targetX;
}else if(camera.x<targetX){
camera.x+=(targetX-camera.x)>>4;
//Make sure we don't go too far.
if(camera.x>targetX)
camera.x=targetX;
}
if(camera.y>targetY){
camera.y-=(camera.y-targetY)>>4;
//Make sure we don't go too far.
if(camera.y<targetY)
camera.y=targetY;
}else if(camera.y<targetY){
camera.y+=(targetY-camera.y)>>4;
//Make sure we don't go too far.
if(camera.y>targetY)
camera.y=targetY;
}
break;
}
//Check if we won.
if(won){
//Check if it's playing from record
if(player.isPlayFromRecord() && !interlevel){
recordingEnded(imageManager,renderer);
}else{
//the string to store auto-save record path.
string bestTimeFilePath,bestRecordingFilePath;
//and if we can't get test path.
bool filePathError=false;
//Get current level
LevelPack::Level *level=levels->getLevel();
//Now check if we should update statistics
{
//Get previous and current medal
int oldMedal=level->won?1:0,newMedal=1;
int bestTime=level->time;
int targetTime=level->targetTime;
int bestRecordings=level->recordings;
int targetRecordings=level->targetRecordings;
if(oldMedal){
if(bestTime>=0 && (targetTime<0 || bestTime<=targetTime))
oldMedal++;
if(bestRecordings>=0 && (targetRecordings<0 || bestRecordings<=targetRecordings))
oldMedal++;
}else{
bestTime=time;
bestRecordings=recordings;
}
if(bestTime<0 || bestTime>time) bestTime=time;
if(bestRecordings<0 || bestRecordings>recordings) bestRecordings=recordings;
if(targetTime<0 || bestTime<=targetTime)
newMedal++;
if(targetRecordings<0 || bestRecordings<=targetRecordings)
newMedal++;
//Check if we need to update statistics
if(newMedal>oldMedal){
switch(oldMedal){
case 0:
statsMgr.completedLevels++;
break;
case 2:
statsMgr.silverLevels--;
break;
}
switch(newMedal){
case 2:
statsMgr.silverLevels++;
break;
case 3:
statsMgr.goldLevels++;
break;
}
}
}
//Check the achievement "Complete a level with checkpoint, but without saving"
if (objLastCheckPoint.get() == NULL) {
for (auto obj : levelObjects) {
if (obj->type == TYPE_CHECKPOINT) {
statsMgr.newAchievement("withoutsave");
break;
}
}
}
//Set the current level won.
level->won=true;
if(level->time==-1 || level->time>time){
level->time=time;
//save the best-time game record.
if(bestTimeFilePath.empty()){
getCurrentLevelAutoSaveRecordPath(bestTimeFilePath,bestRecordingFilePath,true);
}
if(bestTimeFilePath.empty()){
cerr<<"ERROR: Couldn't get auto-save record file path"<<endl;
filePathError=true;
}else{
saveRecord(bestTimeFilePath.c_str());
}
}
if(level->recordings==-1 || level->recordings>recordings){
level->recordings=recordings;
//save the best-recordings game record.
if(bestRecordingFilePath.empty() && !filePathError){
getCurrentLevelAutoSaveRecordPath(bestTimeFilePath,bestRecordingFilePath,true);
}
if(bestRecordingFilePath.empty()){
cerr<<"ERROR: Couldn't get auto-save record file path"<<endl;
filePathError=true;
}else{
saveRecord(bestRecordingFilePath.c_str());
}
}
//Set the next level unlocked if it exists.
if(levels->getCurrentLevel()+1<levels->getLevelCount()){
levels->setLocked(levels->getCurrentLevel()+1);
}
//And save the progress.
levels->saveLevelProgress();
//Now go to the interlevel screen.
replayPlay(imageManager,renderer);
//Update achievements
if(levels->levelpackName=="tutorial") statsMgr.updateTutorialAchievements();
statsMgr.updateLevelAchievements();
//NOTE: We set isReset false to prevent the user from getting a best time of 0.00s and 0 recordings.
isReset = false;
}
}
won=false;
//Check if we should reset.
if (isReset) {
//NOTE: we don't need to reset save ??? it looks like that there are no bugs
reset(false, false);
}
isReset=false;
}
/////////////////RENDER//////////////////
void Game::render(ImageManager&,SDL_Renderer &renderer){
//First of all render the background.
{
//Get a pointer to the background.
ThemeBackground* bg=background;
//Check if the background is null, but there are themes.
if(bg==NULL && objThemes.themeCount()>0){
//Get the background from the first theme in the stack.
bg=objThemes[0]->getBackground(false);
}
//Check if the background isn't null.
if(bg){
//It isn't so draw it.
bg->draw(renderer);
//And if it's the loaded background then also update the animation.
//FIXME: Updating the animation in the render method?
if(bg==background)
bg->updateAnimation();
}else{
//There's no background so fill the screen with white.
SDL_SetRenderDrawColor(&renderer, 255,255,255,255);
SDL_RenderClear(&renderer);
}
}
//Now draw the blackground layers.
auto it = sceneryLayers.begin();
for (; it != sceneryLayers.end(); ++it){
if (it->first >= "f") break; // now we meet a foreground layer
it->second->show(renderer);
}
//Now we draw the levelObjects.
{
//NEW: always render the pushable blocks in front of other blocks
std::vector<Block*> pushableBlocks;
for (auto o : levelObjects) {
if (o->type == TYPE_PUSHABLE) {
pushableBlocks.push_back(o);
} else {
o->show(renderer);
}
}
for (auto o : pushableBlocks) {
o->show(renderer);
}
}
//Followed by the player and the shadow.
//NOTE: We draw the shadow first, because he needs to be behind the player.
shadow.show(renderer);
player.show(renderer);
//Now draw the foreground layers.
for (; it != sceneryLayers.end(); ++it){
it->second->show(renderer);
}
//Show the levelName if it isn't the level editor.
if(stateID!=STATE_LEVEL_EDITOR && bmTips[0]!=NULL && !interlevel){
withTexture(*bmTips[0], [&](SDL_Rect r){
drawGUIBox(-2,SCREEN_HEIGHT-r.h-4,r.w+8,r.h+6,renderer,0xFFFFFFFF);
applyTexture(2,SCREEN_HEIGHT-r.h,*bmTips[0],renderer,NULL);
});
}
//Check if there's a tooltip.
//NOTE: gameTipIndex 0 is used for the levelName, 1 for shadow death, 2 for restart text, 3 for restart+checkpoint.
if(gameTipIndex>3 && gameTipIndex<TYPE_MAX){
//Check if there's a tooltip for the type.
if(bmTips[gameTipIndex]==NULL){
//There isn't thus make it.
string s;
string keyCode = InputManagerKeyCode::describeTwo(inputMgr.getKeyCode(INPUTMGR_ACTION, false), inputMgr.getKeyCode(INPUTMGR_ACTION, true));
transform(keyCode.begin(),keyCode.end(),keyCode.begin(),::toupper);
switch(gameTipIndex){
case TYPE_CHECKPOINT:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to save the game."),keyCode);
break;
case TYPE_SWAP:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to swap the position of player and shadow."),keyCode);
break;
case TYPE_SWITCH:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to activate the switch."),keyCode);
break;
case TYPE_PORTAL:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to teleport."),keyCode);
break;
}
//If we have a string then it's a supported GameObject type.
if(!s.empty()){
bmTips[gameTipIndex]=textureFromText(renderer, *fontText, s.c_str(), objThemes.getTextColor(true));
}
}
//We already have a gameTip for this type so draw it.
if(bmTips[gameTipIndex]!=NULL){
withTexture(*bmTips[gameTipIndex], [&](SDL_Rect r){
drawGUIBox(-2,-2,r.w+8,r.h+6,renderer,0xFFFFFFFF);
applyTexture(2,2,*bmTips[gameTipIndex],renderer);
});
}
}
//Set the gameTip to 0.
gameTipIndex=0;
// Limit the scope of bm, as it's a borrowed pointer.
{
//Pointer to the sdl texture that will contain a message, if any.
SDL_Texture* bm=NULL;
//Check if the player is dead, meaning we draw a message.
if(player.dead){
//Get user configured restart key
string keyCodeRestart = InputManagerKeyCode::describeTwo(inputMgr.getKeyCode(INPUTMGR_RESTART, false), inputMgr.getKeyCode(INPUTMGR_RESTART, true));
transform(keyCodeRestart.begin(),keyCodeRestart.end(),keyCodeRestart.begin(),::toupper);
//The player is dead, check if there's a state that can be loaded.
if(player.canLoadState()){
//Now check if the tip is already made, if not make it.
if(bmTips[3]==NULL){
//Get user defined key for loading checkpoint
string keyCodeLoad = InputManagerKeyCode::describeTwo(inputMgr.getKeyCode(INPUTMGR_LOAD, false), inputMgr.getKeyCode(INPUTMGR_LOAD, true));
transform(keyCodeLoad.begin(),keyCodeLoad.end(),keyCodeLoad.begin(),::toupper);
//Draw string
bmTips[3]=textureFromText(renderer, *fontText,//TTF_RenderUTF8_Blended(fontText,
/// TRANSLATORS: Please do not remove %s from your translation:
/// - first %s means currently configured key to restart game
/// - Second %s means configured key to load from last save
tfm::format(_("Press %s to restart current level or press %s to load the game."),
keyCodeRestart,keyCodeLoad).c_str(),
objThemes.getTextColor(true));
}
bm=bmTips[3].get();
}else{
//Now check if the tip is already made, if not make it.
if(bmTips[2]==NULL){
bmTips[2]=textureFromText(renderer, *fontText,
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with currently configured key to restart game
tfm::format(_("Press %s to restart current level."),keyCodeRestart).c_str(),
objThemes.getTextColor(true));
}
bm=bmTips[2].get();
}
}
//Check if the shadow has died (and there's no other notification).
//NOTE: We use the shadow's jumptime as countdown, this variable isn't used when the shadow is dead.
if(shadow.dead && bm==NULL && shadow.jumpTime>0){
//Now check if the tip is already made, if not make it.
if(bmTips[1]==NULL){
bmTips[1]=textureFromText(renderer, *fontText,
_("Your shadow has died."),
objThemes.getTextColor(true));
}
bm=bmTips[1].get();
//NOTE: Logic in the render loop, we substract the shadow's jumptime by one.
shadow.jumpTime--;
//return view to player and keep it there
cameraMode=CAMERA_PLAYER;
}
//Draw the tip.
if(bm!=NULL){
const SDL_Rect textureSize = rectFromTexture(*bm);
int x=(SCREEN_WIDTH-textureSize.w)/2;
int y=32;
drawGUIBox(x-8,y-8,textureSize.w+16,textureSize.h+14,renderer,0xFFFFFFFF);
applyTexture(x,y,*bm,renderer);
}
}
//Show the number of collectables the user has collected if there are collectables in the level.
//We hide this when interlevel.
if ((currentCollectables || totalCollectables) && !interlevel && time>0){
if (collectablesTexture.needsUpdate(currentCollectables ^ (totalCollectables << 16))) {
collectablesTexture.update(currentCollectables ^ (totalCollectables << 16),
textureFromText(renderer,
*fontText,
tfm::format("%d/%d", currentCollectables, totalCollectables).c_str(),
objThemes.getTextColor(true)));
}
SDL_Rect bmSize = rectFromTexture(*collectablesTexture.get());
//Draw background
drawGUIBox(SCREEN_WIDTH-bmSize.w-34,SCREEN_HEIGHT-bmSize.h-4,bmSize.w+34+2,bmSize.h+4+2,renderer,0xFFFFFFFF);
//Draw the collectable icon
collectable.draw(renderer,SCREEN_WIDTH-50+12,SCREEN_HEIGHT-50+10);
//Draw text
applyTexture(SCREEN_WIDTH-50-bmSize.w+22,SCREEN_HEIGHT-bmSize.h,collectablesTexture.getTexture(),renderer);
}
//show time and records used in level editor or during replay.
if((stateID==STATE_LEVEL_EDITOR || (!interlevel && player.isPlayFromRecord())) && time>0){
const SDL_Color fg=objThemes.getTextColor(true),bg={255,255,255,255};
const int alpha = 160;
if (recordingsTexture.needsUpdate(recordings)) {
recordingsTexture.update(recordings,
textureFromTextShaded(
renderer,
*fontText,
tfm::format(ngettext("%d recording","%d recordings",recordings).c_str(),recordings).c_str(),
fg,
bg
));
SDL_SetTextureAlphaMod(recordingsTexture.get(),alpha);
}
int y=SCREEN_HEIGHT - textureHeight(*recordingsTexture.get());
if (stateID != STATE_LEVEL_EDITOR && bmTips[0] != NULL && !interlevel) {
y -= textureHeight(bmTips[0]) + 4;
}
applyTexture(0,y,*recordingsTexture.get(), renderer);
if(timeTexture.needsUpdate(time)) {
const size_t len = 32;
timeTexture.update(time,
textureFromTextShaded(
renderer,
*fontText,
tfm::format("%-.2fs", time / 40.0).c_str(),
fg,
bg
));
}
y -= textureHeight(*timeTexture.get());
applyTexture(0,y,*timeTexture.get(), renderer);
}
//Draw the current action in the upper right corner.
if(player.record){
const SDL_Rect r = { 0, 0, 50, 50 };
applyTexture(SCREEN_WIDTH - 50, 0, *action, renderer, &r);
} else if (shadow.state != 0){
const SDL_Rect r={50,0,50,50};
applyTexture(SCREEN_WIDTH-50,0,*action,renderer,&r);
}
//if the game is play from record then draw something indicates it
if(player.isPlayFromRecord()){
//Dim the screen if interlevel is true.
if( interlevel){
dimScreen(renderer,191);
}else if((time & 0x10)==0x10){
// FIXME: replace this ugly ad-hoc animation by a better one
const SDL_Rect r={50,0,50,50};
applyTexture(0,0,*action,renderer,&r);
//applyTexture(0,SCREEN_HEIGHT-50,*action,renderer,&r);
//applyTexture(SCREEN_WIDTH-50,SCREEN_HEIGHT-50,*action,renderer,&r);
}
}else if(auto blockId = player.objNotificationBlock.get()){
//If the player is in front of a notification block show the message.
//And it isn't a replay.
//Check if we need to update the notification message texture.
int maxWidth = 0;
int y = 20;
//We check against blockId rather than the full message, as blockId is most likely shorter.
if(notificationTexture.needsUpdate(blockId)) {
const std::string &untranslated_message=blockId->message;
std::string message=_CC(levels->getDictionaryManager(),untranslated_message);
std::vector<std::string> string_data;
//Trim the message.
{
size_t lps = message.find_first_not_of("\n\r \t");
if (lps == string::npos) {
message.clear(); // it's completely empty
} else {
message = message.substr(lps, message.find_last_not_of("\n\r \t") - lps + 1);
}
}
//Split the message into lines.
for (int lps = 0;;) {
// determine the end of line
int lpe = lps;
for (; message[lpe] != '\n' && message[lpe] != '\r' && message[lpe] != '\0'; lpe++);
string_data.push_back(message.substr(lps, lpe - lps));
// break if the string ends
if (message[lpe] == '\0') break;
// skip "\r\n" for Windows line ending
if (message[lpe] == '\r' && message[lpe + 1] == '\n') lpe++;
// point to the start of next line
lps = lpe + 1;
}
vector<SurfacePtr> lines;
//Create the image for each lines
for (int i = 0; i < (int)string_data.size(); i++) {
//Integer used to center the sentence horizontally.
int x = 0;
TTF_SizeUTF8(fontText, string_data[i].c_str(), &x, NULL);
//Find out largest width
if (x>maxWidth)
maxWidth = x;
lines.emplace_back(TTF_RenderUTF8_Blended(fontText, string_data[i].c_str(), objThemes.getTextColor(true)));
//Increase y with 25, about the height of the text.
y += 25;
}
maxWidth+=SCREEN_WIDTH*0.15;
SurfacePtr surf = createSurface(maxWidth, y);
int y1 = y;
for(SurfacePtr &s : lines) {
if(s) {
applySurface((surf->w-s->w)/2,surf->h - y1,s.get(),surf.get(),NULL);
}
y1 -= 25;
}
notificationTexture.update(blockId, textureUniqueFromSurface(renderer,std::move(surf)));
} else {
auto texSize = rectFromTexture(*notificationTexture.get());
maxWidth=texSize.w;
y=texSize.h;
}
drawGUIBox((SCREEN_WIDTH-maxWidth)/2,SCREEN_HEIGHT-y-25,maxWidth,y+20,renderer,0xFFFFFFBF);
applyTexture((SCREEN_WIDTH-maxWidth)/2,SCREEN_HEIGHT-y,notificationTexture.getTexture(),renderer);
}
}
void Game::resize(ImageManager&, SDL_Renderer& /*renderer*/){
//Check if the interlevel popup is shown.
if(interlevel && GUIObjectRoot){
GUIObjectRoot->left=(SCREEN_WIDTH-GUIObjectRoot->width)/2;
}
}
void Game::replayPlay(ImageManager& imageManager,SDL_Renderer& renderer){
//Set interlevel true.
interlevel=true;
//Make a copy of the playerButtons.
vector<int> recordCopy=player.recordButton;
//Reset the game.
//NOTE: We don't reset the saves. I'll see that if it will introduce bugs.
reset(false, false);
//Make the cursor visible when the interlevel popup is up.
SDL_ShowCursor(SDL_ENABLE);
//Set the copy of playerButtons back.
player.recordButton=recordCopy;
//Now play the recording.
player.playRecord();
//Create the gui if it isn't already done.
if(!GUIObjectRoot){
//Create a new GUIObjectRoot the size of the screen.
GUIObjectRoot=new GUIObject(imageManager,renderer,0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//Make child widgets change color properly according to theme.
GUIObjectRoot->inDialog=true;
//Create a GUIFrame for the upper frame.
GUIFrame* upperFrame=new GUIFrame(imageManager,renderer,0,4,0,74);
GUIObjectRoot->addChild(upperFrame);
//Render the You've finished: text and add it to a GUIImage.
//NOTE: The texture is managed by the GUIImage so no need to free it ourselfs.
auto bm = SharedTexture(textureFromText(renderer, *fontGUI,_("You've finished:"),objThemes.getTextColor(true)));
const SDL_Rect textureSize = rectFromTexture(*bm);
GUIImage* title=new GUIImage(imageManager,renderer,0,4-GUI_FONT_RAISE,textureSize.w,textureSize.h,bm);
upperFrame->addChild(title);
//Create the sub title.
string s;
if (levels->getLevelCount()>0){
/// TRANSLATORS: Please do not remove %s or %d from your translation:
/// - %d means the level number in a levelpack
/// - %s means the name of current level
s=tfm::format(_("Level %d %s"),levels->getCurrentLevel()+1,_CC(levels->getDictionaryManager(),levelName));
}
GUIObject* obj=new GUILabel(imageManager,renderer,0,44,0,28,s.c_str(),0,true,true,GUIGravityCenter);
upperFrame->addChild(obj);
obj->render(renderer,0,0,false);
//Determine the width the upper frame should have.
int width;
if(textureSize.w>obj->width)
width=textureSize.w+32;
else
width=obj->width+32;
//Set the left of the title.
title->left=(width-title->width)/2;
//Set the width of the level label to the width of the frame for centering.
obj->width=width;
//Now set the position and width of the frame.
upperFrame->width=width;
upperFrame->left=(SCREEN_WIDTH-width)/2;
//Now create a GUIFrame for the lower frame.
GUIFrame* lowerFrame=new GUIFrame(imageManager,renderer,0,SCREEN_HEIGHT-140,570,135);
GUIObjectRoot->addChild(lowerFrame);
//The different values.
int bestTime=levels->getLevel()->time;
int targetTime=levels->getLevel()->targetTime;
int bestRecordings=levels->getLevel()->recordings;
int targetRecordings=levels->getLevel()->targetRecordings;
int medal=1;
if(bestTime>=0 && (targetTime<0 || bestTime<=targetTime))
medal++;
if(bestRecordings>=0 && (targetRecordings<0 || bestRecordings<=targetRecordings))
medal++;
int maxWidth=0;
int x=20;
//Is there a target time for this level?
int timeY=0;
bool isTargetTime=true;
if(targetTime<0){
isTargetTime=false;
timeY=12;
}
//Create the labels with the time and best time.
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
obj=new GUILabel(imageManager,renderer,x,10+timeY,-1,36,tfm::format(_("Time: %-.2fs"),time/40.0).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
obj=new GUILabel(imageManager,renderer,x,34+timeY,-1,36,tfm::format(_("Best time: %-.2fs"),bestTime/40.0).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
if(isTargetTime){
obj=new GUILabel(imageManager,renderer,x,58,-1,36,tfm::format(_("Target time: %-.2fs"),targetTime/40.0).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
}
x+=maxWidth+20;
//Is there target recordings for this level?
int recsY=0;
bool isTargetRecs=true;
if(targetRecordings<0){
isTargetRecs=false;
recsY=12;
}
//Now the ones for the recordings.
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
obj=new GUILabel(imageManager,renderer,x,10+recsY,-1,36,tfm::format(_("Recordings: %d"),recordings).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
obj=new GUILabel(imageManager,renderer,x,34+recsY,-1,36,tfm::format(_("Best recordings: %d"),bestRecordings).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
if(isTargetRecs){
obj=new GUILabel(imageManager,renderer,x,58,-1,36,tfm::format(_("Target recordings: %d"),targetRecordings).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
}
x+=maxWidth;
//The medal that is earned.
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with name of a prize medal (gold, silver or bronze)
string s1=tfm::format(_("You earned the %s medal"),(medal>1)?(medal==3)?_("GOLD"):_("SILVER"):_("BRONZE"));
obj=new GUILabel(imageManager,renderer,50,92,-1,36,s1.c_str(),0,true,true,GUIGravityCenter);
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->left+obj->width>x){
x=obj->left+obj->width+30;
}else{
obj->left=20+(x-20-obj->width)/2;
}
//Create the rectangle for the earned medal.
SDL_Rect r;
r.x=(medal-1)*30;
r.y=0;
r.w=30;
r.h=30;
//Create the medal on the left side.
obj=new GUIImage(imageManager,renderer,16,92,30,30,medals,r);
lowerFrame->addChild(obj);
//And the medal on the right side.
obj=new GUIImage(imageManager,renderer,x-24,92,30,30,medals,r);
lowerFrame->addChild(obj);
//Create the three buttons, Menu, Restart, Next.
/// TRANSLATORS: used as return to the level selector menu
GUIObject* b1=new GUIButton(imageManager,renderer,x,10,-1,36,_("Menu"),0,true,true,GUIGravityCenter);
b1->name="cmdMenu";
b1->eventCallback=this;
lowerFrame->addChild(b1);
b1->render(renderer,0,0,true);
/// TRANSLATORS: used as restart level
GUIObject* b2=new GUIButton(imageManager,renderer,x,50,-1,36,_("Restart"),0,true,true,GUIGravityCenter);
b2->name="cmdRestart";
b2->eventCallback=this;
lowerFrame->addChild(b2);
b2->render(renderer,0,0,true);
/// TRANSLATORS: used as next level
GUIObject* b3=new GUIButton(imageManager,renderer,x,90,-1,36,_("Next"),0,true,true,GUIGravityCenter);
b3->name="cmdNext";
b3->eventCallback=this;
lowerFrame->addChild(b3);
b3->render(renderer,0,0,true);
maxWidth=b1->width;
if(b2->width>maxWidth)
maxWidth=b2->width;
if(b3->width>maxWidth)
maxWidth=b3->width;
b1->left=b2->left=b3->left=x+maxWidth/2;
x+=maxWidth;
lowerFrame->width=x;
lowerFrame->left=(SCREEN_WIDTH-lowerFrame->width)/2;
}
}
void Game::recordingEnded(ImageManager& imageManager, SDL_Renderer& renderer){
//Check if it's a normal replay, if so just stop.
if(!interlevel){
//Show the cursor so that the user can press the ok button.
SDL_ShowCursor(SDL_ENABLE);
//Now show the message box.
msgBox(imageManager,renderer,_("Game replay is done."),MsgBoxOKOnly,_("Game Replay"));
//Go to the level select menu.
setNextState(STATE_LEVEL_SELECT);
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}else{
//Instead of directly replaying we set won true to let the Game handle the replaying at the end of the update cycle.
won=true;
}
}
bool Game::canSaveState(){
return (player.canSaveState() && shadow.canSaveState());
}
bool Game::saveState(){
//Check if the player and shadow can save the current state.
if(canSaveState()){
//Let the player and the shadow save their state.
player.saveState();
shadow.saveState();
//Save the stats.
timeSaved=time;
recordingsSaved=recordings;
recentSwapSaved=recentSwap;
+ //Save the PRNG and seed.
+ prngSaved = prng;
+ prngSeedSaved = prngSeed;
+
//Save the level size.
levelRectSaved = levelRect;
//Save the camera mode and target.
cameraModeSaved=cameraMode;
cameraTargetSaved=cameraTarget;
//Save the current collectables
currentCollectablesSaved = currentCollectables;
totalCollectablesSaved = totalCollectables;
//Save scripts.
copyCompiledScripts(getScriptExecutor()->getLuaState(), compiledScripts, savedCompiledScripts);
//Save other state, for example moving blocks.
copyLevelObjects(levelObjects, levelObjectsSave, false);
//Also save states of scenery layers.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it) {
it->second->saveAnimation();
}
//Also save the background animation, if any.
if(background)
background->saveAnimation();
if(!player.isPlayFromRecord() && !interlevel){
//Update achievements
Uint32 t=SDL_GetTicks()+5000; //Add a bias to prevent bugs
if(recentSave+1000>t){
statsMgr.newAchievement("panicSave");
}
recentSave=t;
//Update statistics.
statsMgr.saveTimes++;
//Update achievements
switch(statsMgr.saveTimes){
case 1000:
statsMgr.newAchievement("save1k");
break;
}
}
//Save the state for script executor.
getScriptExecutor()->saveState();
//Execute the onSave event.
executeScript(LevelEvent_OnSave);
//Return true.
return true;
}
//We can't save the state so return false.
return false;
}
bool Game::loadState(){
//Check if there's a state that can be loaded.
if(player.canLoadState() && shadow.canLoadState()){
//Let the player and the shadow load their state.
player.loadState();
shadow.loadState();
//Load the stats.
time=timeSaved;
recordings=recordingsSaved;
recentSwap=recentSwapSaved;
+ //Load the PRNG and seed.
+ prng = prngSaved;
+ prngSeed = prngSeedSaved;
+
//Load the level size.
levelRect = levelRectSaved;
//Load the camera mode and target.
cameraMode=cameraModeSaved;
cameraTarget=cameraTargetSaved;
//Load the current collactbles
currentCollectables = currentCollectablesSaved;
totalCollectables = totalCollectablesSaved;
//Load scripts.
copyCompiledScripts(getScriptExecutor()->getLuaState(), savedCompiledScripts, compiledScripts);
//Load other state, for example moving blocks.
copyLevelObjects(levelObjectsSave, levelObjects, true);
//Also load states of scenery layers.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it) {
it->second->loadAnimation();
}
//Also load the background animation, if any.
if(background)
background->loadAnimation();
if(!player.isPlayFromRecord() && !interlevel){
//Update achievements.
Uint32 t=SDL_GetTicks()+5000; //Add a bias to prevent bugs
if(recentLoad+1000>t){
statsMgr.newAchievement("panicLoad");
}
recentLoad=t;
//Update statistics.
statsMgr.loadTimes++;
//Update achievements
switch(statsMgr.loadTimes){
case 1000:
statsMgr.newAchievement("load1k");
break;
}
}
//Load the state for script executor.
getScriptExecutor()->loadState();
//Execute the onLoad event, if any.
executeScript(LevelEvent_OnLoad);
//Return true.
return true;
}
//We can't load the state so return false.
return false;
}
+static std::string createNewSeed() {
+ static int createSeedTime = 0;
+
+ struct Buffer {
+ time_t systemTime;
+ Uint32 sdlTicks;
+ int x;
+ int y;
+ int createSeedTime;
+ } buffer;
+
+ buffer.systemTime = time(NULL);
+ buffer.sdlTicks = SDL_GetTicks();
+ SDL_GetMouseState(&buffer.x, &buffer.y);
+ buffer.createSeedTime = ++createSeedTime;
+
+ return Md5::toString(Md5::calc(&buffer, sizeof(buffer), NULL));
+}
+
void Game::reset(bool save,bool noScript){
//Some sanity check, i.e. if we switch from no-script mode to script mode, we should always reset the save
assert(noScript || getScriptExecutor() || save);
//We need to reset the game so we also reset the player and the shadow.
player.reset(save);
shadow.reset(save);
saveStateNextTime=false;
loadStateNextTime=false;
//Reset the stats if interlevel isn't true.
if(!interlevel){
time=0;
recordings=0;
}
recentSwap=-10000;
if(save) recentSwapSaved=-10000;
+ //Reset the pseudo-random number generator by creating a new seed, unless we are playing from record.
+ if (levelFile == "?record?" || interlevel) {
+ if (prngSeed.empty()) {
+ cout << "WARNING: The record file doesn't provide a random seed! Will create a new random seed!"
+ "This may breaks the behavior pseudo-random number generator in script!" << endl;
+ prngSeed = createNewSeed();
+ } else {
+#ifdef _DEBUG
+ cout << "Use existing PRNG seed: " << prngSeed << endl;
+#endif
+ }
+ } else {
+ prngSeed = createNewSeed();
+#ifdef _DEBUG
+ cout << "Create new PRNG seed: " << prngSeed << endl;
+#endif
+ }
+ prng.seed(std::seed_seq(prngSeed.begin(), prngSeed.end()));
+ if (save) {
+ prngSaved = prng;
+ prngSeedSaved = prngSeed;
+ }
+
//Reset the level size.
levelRect = levelRectInitial;
if (save) levelRectSaved = levelRectInitial;
//Reset the camera.
cameraMode=CAMERA_PLAYER;
if(save) cameraModeSaved=CAMERA_PLAYER;
cameraTarget = SDL_Rect{ 0, 0, 0, 0 };
if (save) cameraTargetSaved = SDL_Rect{ 0, 0, 0, 0 };
//Reset the number of collectables
currentCollectables = currentCollectablesInitial;
totalCollectables = totalCollectablesInitial;
if (save) {
currentCollectablesSaved = currentCollectablesInitial;
totalCollectablesSaved = totalCollectablesInitial;
}
//Clear the event queue, since all the events are from before the reset.
eventQueue.clear();
//Reset states of scenery layers.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it) {
it->second->resetAnimation(save);
}
//Also reset the background animation, if any.
if(background)
background->resetAnimation(save);
//Reset the cached notification block
notificationTexture.update(NULL, NULL);
//Reset the script environment if necessary.
if (noScript) {
//Destroys the script environment completely.
scriptExecutor->destroy();
//Clear the level script.
compiledScripts.clear();
savedCompiledScripts.clear();
initialCompiledScripts.clear();
//Clear the block script.
for (auto block : levelObjects){
block->compiledScripts.clear();
}
for (auto block : levelObjectsSave){
block->compiledScripts.clear();
}
for (auto block : levelObjectsInitial){
block->compiledScripts.clear();
}
} else if (save) {
//Create a new script environment.
getScriptExecutor()->reset(true);
//Recompile the level script.
compiledScripts.clear();
savedCompiledScripts.clear();
initialCompiledScripts.clear();
for (auto it = scripts.begin(); it != scripts.end(); ++it){
int index = getScriptExecutor()->compileScript(it->second);
compiledScripts[it->first] = index;
lua_rawgeti(getScriptExecutor()->getLuaState(), LUA_REGISTRYINDEX, index);
savedCompiledScripts[it->first] = luaL_ref(getScriptExecutor()->getLuaState(), LUA_REGISTRYINDEX);
lua_rawgeti(getScriptExecutor()->getLuaState(), LUA_REGISTRYINDEX, index);
initialCompiledScripts[it->first] = luaL_ref(getScriptExecutor()->getLuaState(), LUA_REGISTRYINDEX);
}
//Recompile the block script.
for (auto block : levelObjects){
block->compiledScripts.clear();
}
for (auto block : levelObjectsSave){
block->compiledScripts.clear();
}
for (auto block : levelObjectsInitial) {
block->compiledScripts.clear();
for (auto it = block->scripts.begin(); it != block->scripts.end(); ++it){
int index = getScriptExecutor()->compileScript(it->second);
block->compiledScripts[it->first] = index;
}
}
} else {
assert(getScriptExecutor());
//Do a soft reset.
getScriptExecutor()->reset(false);
//Restore the level script to initial state.
copyCompiledScripts(getScriptExecutor()->getLuaState(), initialCompiledScripts, compiledScripts);
//NOTE: We don't need to restore the block script since it will be restored automatically when the block array is copied.
}
//We reset levelObjects here since we need to wait the compiledScripts being initialized.
copyLevelObjects(levelObjectsInitial, levelObjects, true);
if (save) {
copyLevelObjects(levelObjectsInitial, levelObjectsSave, false);
}
//Also reset the last checkpoint so set it to NULL.
if (save)
objLastCheckPoint = NULL;
//Call the level's onCreate event.
executeScript(LevelEvent_OnCreate);
//Send GameObjectEvent_OnCreate event to the script
for (auto block : levelObjects) {
block->onEvent(GameObjectEvent_OnCreate);
}
//Close exit(s) if there are any collectables
if (totalCollectables>0){
for (auto block : levelObjects){
if (block->type == TYPE_EXIT){
block->onEvent(GameObjectEvent_OnSwitchOff);
}
}
}
//Hide the cursor (if not the leveleditor).
if(stateID!=STATE_LEVEL_EDITOR)
SDL_ShowCursor(SDL_DISABLE);
}
void Game::executeScript(int eventType){
map<int,int>::iterator it;
//Check if there's a script for the given event.
it=compiledScripts.find(eventType);
if(it!=compiledScripts.end()){
//There is one so execute it.
getScriptExecutor()->executeScript(it->second);
}
}
void Game::broadcastObjectEvent(int eventType,int objectType,const char* id,GameObject* target){
//Create a typeGameObjectEvent that can be put into the queue.
typeGameObjectEvent e;
//Set the event type.
e.eventType=eventType;
//Set the object type.
e.objectType=objectType;
//By default flags=0.
e.flags=0;
//Unless there's an id.
if(id){
//Set flags to 0x1 and set the id.
e.flags|=1;
e.id=id;
}
//Or there's a target given.
if(target)
e.target=target;
else
e.target=NULL;
//Add the event to the queue.
eventQueue.push_back(e);
}
void Game::getCurrentLevelAutoSaveRecordPath(std::string &bestTimeFilePath,std::string &bestRecordingFilePath,bool createPath){
levels->getLevelAutoSaveRecordPath(-1,bestTimeFilePath,bestRecordingFilePath,createPath);
}
void Game::gotoNextLevel(ImageManager& imageManager, SDL_Renderer& renderer){
//Goto the next level.
levels->nextLevel();
//Check if the level exists.
if(levels->getCurrentLevel()<levels->getLevelCount()){
setNextState(STATE_GAME);
}else{
if(!levels->congratulationText.empty()){
msgBox(imageManager,renderer,_CC(levels->getDictionaryManager(),levels->congratulationText),MsgBoxOKOnly,_("Congratulations"));
}else{
msgBox(imageManager,renderer,_("You have finished the levelpack!"),MsgBoxOKOnly,_("Congratulations"));
}
//Now go back to the levelselect screen.
setNextState(STATE_LEVEL_SELECT);
//And set the music back to menu.
getMusicManager()->playMusic("menu");
}
}
void Game::GUIEventCallback_OnEvent(ImageManager& imageManager,SDL_Renderer& renderer, string name,GUIObject* obj,int eventType){
if(name=="cmdMenu"){
setNextState(STATE_LEVEL_SELECT);
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}else if(name=="cmdRestart"){
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
interlevel=false;
//And reset the game.
//NOTE: We don't need to clear the save game because in level replay the game won't be saved (??)
//TODO: it seems work (??); I'll see if it introduce bugs
reset(false, false);
}else if(name=="cmdNext"){
//No matter what, clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//And goto the next level.
gotoNextLevel(imageManager,renderer);
}
}
void Game::invalidateNotificationTexture(Block *block) {
if (block == NULL || block == notificationTexture.getId()) {
notificationTexture.update(NULL, NULL);
}
}
diff --git a/src/Game.h b/src/Game.h
index 0a3cafc..557a97b 100644
--- a/src/Game.h
+++ b/src/Game.h
@@ -1,314 +1,321 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GAME_H
#define GAME_H
#include <SDL.h>
#include <array>
#include <vector>
#include <map>
#include <string>
+#include <random>
#include "CachedTexture.h"
#include "GameState.h"
#include "GUIObject.h"
#include "Block.h"
#include "Scenery.h"
#include "SceneryLayer.h"
#include "Player.h"
#include "Render.h"
#include "Shadow.h"
//This structure contains variables that make a GameObjectEvent.
struct typeGameObjectEvent{
//The type of event.
int eventType;
//The type of object that should react to the event.
int objectType;
//Flags, 0x1 means use the id.
int flags;
//Blocks with this id should react to the event.
std::string id;
//Optional pointer to the block the event should be called on.
GameObject* target;
};
class ThemeManager;
class ThemeBackground;
class TreeStorageNode;
class ScriptExecutor;
//The different level events.
enum LevelEventType{
//Event called when the level is created or the game is reset. This happens after all the blocks are created and their onCreate is called.
LevelEvent_OnCreate=1,
//Event called when the game is saved.
LevelEvent_OnSave,
//Event called when the game is loaded.
LevelEvent_OnLoad,
};
class Game : public GameState,public GUIEventCallback{
private:
//Boolean if the game should reset. This happens when player press 'R' button.
bool isReset;
//The script executor.
ScriptExecutor *scriptExecutor;
protected:
//contains currently played level.
TreeStorageNode* currentLevelNode;
//Array containing "tooltips" for certain block types.
//It will be shown in the topleft corner of the screen.
std::array<TexturePtr, TYPE_MAX> bmTips;
//Texture containing the action images (record, play, etc..)
SharedTexture action;
//Texture containing the medal image.
SharedTexture medals;
//ThemeBlockInstance containing the collectable image.
ThemeBlockInstance collectable;
//The name of the current level.
std::string levelName;
//The path + file of the current level.
std::string levelFile;
//Editor data containing information like name, size, etc...
std::map<std::string,std::string> editorData;
//Vector used to queue the gameObjectEvents.
std::vector<typeGameObjectEvent> eventQueue;
//The themeManager.
ThemeManager* customTheme;
//The themeBackground.
ThemeBackground* background;
CachedTexture<int> collectablesTexture;
CachedTexture<int> recordingsTexture;
CachedTexture<int> timeTexture;
CachedTexture<Block*> notificationTexture;
//Load a level from node.
//After calling this function the ownership of
//node will transfer to Game class. So don't delete
//the node after calling this function!
virtual void loadLevelFromNode(ImageManager& imageManager, SDL_Renderer& renderer, TreeStorageNode* obj, const std::string& fileName);
//(internal function) Reload the music according to level music and level pack music.
//This is mainly used in level editor.
void reloadMusic();
public:
//Array used to convert GameObject type->string.
static const char* blockName[TYPE_MAX];
//Map used to convert GameObject string->type.
static std::map<std::string,int> blockNameMap;
//Map used to convert GameObjectEventType type->string.
static std::map<int,std::string> gameObjectEventTypeMap;
//Map used to convert GameObjectEventType string->type.
static std::map<std::string,int> gameObjectEventNameMap;
//Map used to convert LevelEventType type->string.
static std::map<int,std::string> levelEventTypeMap;
//Map used to convert LevelEventType string->type.
static std::map<std::string,int> levelEventNameMap;
//The level rect.
//NOTE: the x,y of these rects can only be changed by script.
//If not changed by script, they are always 0,0.
SDL_Rect levelRect, levelRectSaved, levelRectInitial;
+ //The pseudo-random number generator which is mainly used in script.
+ std::mt19937 prng, prngSaved;
+
+ //The seed of the pseudo-random number generator, which will be saved to and load from replay.
+ std::string prngSeed, prngSeedSaved;
+
//Boolean that is set to true when a game is won.
bool won;
//Boolean that is set to true when we should save game on next logic update.
bool saveStateNextTime;
//Boolean that is set to true when we should load game on next logic update.
bool loadStateNextTime;
//Boolean if the replaying currently done is for the interlevel screen.
bool interlevel;
//Integer containing the current tip index.
int gameTipIndex;
//Integer containing the number of ticks passed since the start of the level.
int time;
//Integer containing the stored value of time.
int timeSaved;
//Integer containing the number of recordings it took to finish.
int recordings;
//Integer containing the stored value of recordings.
int recordingsSaved;
//Integer keeping track of currently obtained collectables
int currentCollectables, currentCollectablesSaved, currentCollectablesInitial;
//Integer keeping track of total colletables in the level
int totalCollectables, totalCollectablesSaved, totalCollectablesInitial;
//Time of recent swap, for achievements. (in game-ticks)
int recentSwap,recentSwapSaved;
//Store time of recent save/load for achievements (in millisecond)
Uint32 recentLoad,recentSave;
//Enumeration with the different camera modes.
enum CameraMode{
CAMERA_PLAYER,
CAMERA_SHADOW,
CAMERA_CUSTOM
};
//The current camera mode.
CameraMode cameraMode;
//Rectangle containing the target for the camera.
SDL_Rect cameraTarget;
//The saved cameraMode.
CameraMode cameraModeSaved;
SDL_Rect cameraTargetSaved;
//Level scripts.
std::map<int,std::string> scripts;
//Compiled scripts. Use lua_rawgeti(L, LUA_REGISTRYINDEX, r) to get the function.
std::map<int, int> compiledScripts, savedCompiledScripts, initialCompiledScripts;
//Vector containing all the levelObjects in the current game.
std::vector<Block*> levelObjects, levelObjectsSave, levelObjectsInitial;
//The layers for the scenery.
// We utilize the fact that std::map is sorted, and we compare the layer name with "f",
// If name<"f" then it's background layer, if name>="f" then it's foreground layer.
// NOTE: the layer name is case sensitive, in particular if the name start with capital "F"
// then it is still background layer.
std::map<std::string,SceneryLayer*> sceneryLayers;
//The player...
Player player;
//... and his shadow.
Shadow shadow;
//warning: weak reference only, may point to invalid location
Block::ObservePointer objLastCheckPoint;
//Constructor.
Game(SDL_Renderer& renderer, ImageManager& imageManager);
//If this is not empty then when next Game class is created
//it will play this record file.
static std::string recordFile;
//Destructor.
//It will call destroy();
~Game();
//Method used to clean up the GameState.
void destroy();
//Inherited from GameState.
void handleEvents(ImageManager&imageManager, SDL_Renderer&renderer) override;
void logic(ImageManager& imageManager, SDL_Renderer& renderer) override;
void render(ImageManager&,SDL_Renderer& renderer) override;
void resize(ImageManager& imageManager, SDL_Renderer& renderer) override;
//This method will load a level.
//fileName: The fileName of the level.
virtual void loadLevel(ImageManager& imageManager, SDL_Renderer& renderer, std::string fileName);
//Method used to broadcast a GameObjectEvent.
//eventType: The type of event.
//objectType: The type of object that should react to the event.
//id: The id of the blocks that should react.
//target: Pointer to the object
void broadcastObjectEvent(int eventType,int objectType=-1,const char* id=NULL,GameObject* target=NULL);
//Compile all scripts and run onCreate script.
//NOTE: Call this function only after script state reset, or there will be some memory leaks.
void compileScript();
//Method that will check if a script for a given levelEvent is present.
//If that's the case the script will be executed.
//eventType: The level event type to execute.
void inline executeScript(int eventType);
//Returns if the player and shadow can save the current state.
bool canSaveState();
//Method used to store the current state.
//This is used for checkpoints.
//Returns: True if it succeeds without problems.
bool saveState();
//Method used to load the stored state.
//This is used for checkpoints.
//Returns: True if it succeeds without problems.
bool loadState();
//Method that will reset the GameState to it's initial state.
//save: Boolean if the saved state should also be deleted. This also means recreate the Lua context.
//noScript: Boolean if we should not compile the script at all. This is used by level editor when exiting test play.
void reset(bool save,bool noScript);
//Save current game record to the file.
//fileName: The filename of the destination file.
void saveRecord(const char* fileName);
//Load game record (and its level) from file and play it.
//fileName: The filename of the recording file.
void loadRecord(ImageManager& imageManager, SDL_Renderer& renderer, const char* fileName);
//Method called by the player (or shadow) when he finished.
void replayPlay(ImageManager& imageManager, SDL_Renderer &renderer);
//Method that gets called when the recording has ended.
void recordingEnded(ImageManager& imageManager, SDL_Renderer& renderer);
//get current level's auto-save record path,
//using current level's MD5, file name and other information.
void getCurrentLevelAutoSaveRecordPath(std::string &bestTimeFilePath,std::string &bestRecordingFilePath,bool createPath);
//Method that will prepare the gamestate for the next level and start it.
//If it's the last level it will show the congratulations text and return to the level select screen.
void gotoNextLevel(ImageManager& imageManager, SDL_Renderer& renderer);
//Get the name of the current level.
const std::string& getLevelName(){
return levelName;
}
//GUI event handling is done here.
void GUIEventCallback_OnEvent(ImageManager&imageManager, SDL_Renderer&renderer, std::string name, GUIObject* obj, int eventType) override;
//Get the script executor.
ScriptExecutor* getScriptExecutor() {
return scriptExecutor;
}
//Invalidates the notification texture.
//block: The block which is updated. The cached texture won't be invalidated if it's not for this block.
//NULL means invalidates the texture no matter which block is updated.
void invalidateNotificationTexture(Block *block = NULL);
};
#endif
diff --git a/src/ScriptAPI.cpp b/src/ScriptAPI.cpp
index 57d49ed..64e8994 100644
--- a/src/ScriptAPI.cpp
+++ b/src/ScriptAPI.cpp
@@ -1,3183 +1,3284 @@
/*
* Copyright (C) 2012-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ScriptAPI.h"
#include "ScriptExecutor.h"
#include "SoundManager.h"
#include "Functions.h"
#include "Block.h"
#include "Game.h"
#include "MusicManager.h"
#include "ScriptDelayExecution.h"
#include "Globals.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include <iostream>
#include <sstream>
#include <algorithm>
using namespace std;
/////////////////////////// HELPER MACRO ///////////////////////////
#define HELPER_GET_AND_CHECK_ARGS(ARGS) \
const int args = lua_gettop(state); \
if(args != ARGS) { \
return luaL_error(state, "Incorrect number of arguments for %s, expected %d.", __FUNCTION__, ARGS); \
}
#define HELPER_GET_AND_CHECK_ARGS_RANGE(ARGS1, ARGS2) \
const int args = lua_gettop(state); \
if(args < ARGS1 || args > ARGS2) { \
return luaL_error(state, "Incorrect number of arguments for %s, expected %d-%d.", __FUNCTION__, ARGS1, ARGS2); \
}
#define HELPER_GET_AND_CHECK_ARGS_2(ARGS1, ARGS2) \
const int args = lua_gettop(state); \
if(args != ARGS1 && args != ARGS2) { \
return luaL_error(state, "Incorrect number of arguments for %s, expected %d or %d.", __FUNCTION__, ARGS1, ARGS2); \
}
#define HELPER_GET_AND_CHECK_ARGS_AT_LEAST(ARGS) \
const int args = lua_gettop(state); \
if(args < ARGS) { \
return luaL_error(state, "Incorrect number of arguments for %s, expected at least %d.", __FUNCTION__, ARGS); \
}
#define HELPER_GET_AND_CHECK_ARGS_AT_MOST(ARGS) \
const int args = lua_gettop(state); \
if(args > ARGS) { \
return luaL_error(state, "Incorrect number of arguments for %s, expected at most %d.", __FUNCTION__, ARGS); \
}
//================================================================
#define HELPER_CHECK_ARGS_TYPE(INDEX, TYPE) \
if(!lua_is##TYPE(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s, should be %s.", INDEX, __FUNCTION__, #TYPE); \
}
#define HELPER_CHECK_ARGS_TYPE_NO_HINT(INDEX, TYPE) \
if(!lua_is##TYPE(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s.", INDEX, __FUNCTION__); \
}
#define HELPER_CHECK_ARGS_TYPE_2(INDEX, TYPE1, TYPE2) \
if(!lua_is##TYPE1(state,INDEX) && !lua_is##TYPE2(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s, should be %s or %s.", INDEX, __FUNCTION__, #TYPE1, #TYPE2); \
}
#define HELPER_CHECK_ARGS_TYPE_2_NO_HINT(INDEX, TYPE1, TYPE2) \
if(!lua_is##TYPE1(state,INDEX) && !lua_is##TYPE2(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s.", INDEX, __FUNCTION__); \
}
#define HELPER_CHECK_ARGS_TYPE_OR_NIL(INDEX, TYPE) \
HELPER_CHECK_ARGS_TYPE_2(INDEX, TYPE, nil)
#define HELPER_CHECK_ARGS_TYPE_OR_NIL_NO_HINT(INDEX, TYPE) \
HELPER_CHECK_ARGS_TYPE_2_NO_HINT(INDEX, TYPE, nil)
//================================================================
#define HELPER_CHECK_OPTIONAL_ARGS_TYPE(INDEX, TYPE) \
if(args>=INDEX && !lua_is##TYPE(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s, should be %s.", INDEX, __FUNCTION__, #TYPE); \
}
#define HELPER_CHECK_OPTIONAL_ARGS_TYPE_NO_HINT(INDEX, TYPE) \
if(args>=INDEX && !lua_is##TYPE(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s.", INDEX, __FUNCTION__); \
}
#define HELPER_CHECK_OPTIONAL_ARGS_TYPE_2(INDEX, TYPE1, TYPE2) \
if(args>=INDEX && !lua_is##TYPE1(state,INDEX) && !lua_is##TYPE2(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s, should be %s or %s.", INDEX, __FUNCTION__, #TYPE1, #TYPE2); \
}
#define HELPER_CHECK_OPTIONAL_ARGS_TYPE_2_NO_HINT(INDEX, TYPE1, TYPE2) \
if(args>=INDEX && !lua_is##TYPE1(state,INDEX) && !lua_is##TYPE2(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s.", INDEX, __FUNCTION__); \
}
#define HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL(INDEX, TYPE) \
HELPER_CHECK_OPTIONAL_ARGS_TYPE_2(INDEX, TYPE, nil)
#define HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL_NO_HINT(INDEX, TYPE) \
HELPER_CHECK_OPTIONAL_ARGS_TYPE_2_NO_HINT(INDEX, TYPE, nil)
//================================================================
#define HELPER_REGISTER_IS_VALID_FUNCTION(CLASS) \
int isValid(lua_State* state){ \
HELPER_GET_AND_CHECK_ARGS(1); \
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata); \
CLASS* object = CLASS::getObjectFromUserData(state, 1); \
lua_pushboolean(state, object ? 1 : 0); \
return 1; \
}
//================================================================
#define _F(FUNC) { #FUNC, _L::FUNC }
#define _FG(FUNC) _F(get##FUNC)
#define _FI(FUNC) _F(is##FUNC)
#define _FS(FUNC) _F(set##FUNC)
#define _FGS(FUNC) _F(get##FUNC), _F(set##FUNC)
#define _FIS(FUNC) _F(is##FUNC), _F(set##FUNC)
///////////////////////////BLOCK SPECIFIC///////////////////////////
class BlockScriptAPI {
public:
static int getFlags(const Block* block) {
return block->flags;
}
static void setFlags(Block* block, int flags) {
block->flags = flags;
}
static void fragileUpdateState(Block* block, int state) {
state &= 0x3;
block->flags = (block->flags & ~0x3) | state;
const char* s = (state == 0) ? "default" : ((state == 1) ? "fragile1" : ((state == 2) ? "fragile2" : "fragile3"));
block->appearance.changeState(s);
}
static int getTemp(const Block* block) {
return block->temp;
}
static void setTemp(Block* block, int value) {
block->temp = value;
}
static int getSpeed(const Block* block) {
return block->speed;
}
static void setSpeed(Block* block, int value) {
block->speed = value;
}
static void invalidatePathMaxTime(Block* block) {
block->movingPosTime = -1;
}
static std::vector<SDL_Rect>& getMovingPos(Block* block) {
return block->movingPos;
}
};
namespace block {
HELPER_REGISTER_IS_VALID_FUNCTION(Block);
int getBlockById(lua_State* state){
//Get the number of args, this MUST be one.
HELPER_GET_AND_CHECK_ARGS(1);
//Make sure the given argument is an id (string).
HELPER_CHECK_ARGS_TYPE(1, string);
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Get the actual game object.
string id = lua_tostring(state, 1);
std::vector<Block*>& levelObjects = game->levelObjects;
Block* object = NULL;
for (unsigned int i = 0; i < levelObjects.size(); i++){
if (levelObjects[i]->getEditorProperty("id") == id){
object = levelObjects[i];
break;
}
}
if (object == NULL){
//Unable to find the requested object.
//Return nothing, will result in a nil in the script.
return 0;
}
//Create the userdatum.
object->createUserData(state, "block");
//We return one object, the userdatum.
return 1;
}
int getBlocksById(lua_State* state){
//Get the number of args, this MUST be one.
HELPER_GET_AND_CHECK_ARGS(1);
//Make sure the given argument is an id (string).
HELPER_CHECK_ARGS_TYPE(1, string);
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Get the actual game object.
string id = lua_tostring(state, 1);
std::vector<Block*>& levelObjects = game->levelObjects;
std::vector<Block*> result;
for (unsigned int i = 0; i < levelObjects.size(); i++){
if (levelObjects[i]->getEditorProperty("id") == id){
result.push_back(levelObjects[i]);
}
}
//Create the table that will hold the result.
lua_createtable(state, result.size(), 0);
//Loop through the results.
for (unsigned int i = 0; i < result.size(); i++){
//Create the userdatum.
result[i]->createUserData(state, "block");
//And set the table.
lua_rawseti(state, -2, i + 1);
}
//We return one object, the userdatum.
return 1;
}
int moveTo(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number);
HELPER_CHECK_ARGS_TYPE(3, number);
//Now get the pointer to the object.
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
int x = lua_tonumber(state, 2);
int y = lua_tonumber(state, 3);
object->moveTo(x, y);
return 0;
}
int getLocation(lua_State* state){
//Make sure there's only one argument and that argument is an userdatum.
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Get the object.
SDL_Rect r = object->getBox();
lua_pushinteger(state, r.x);
lua_pushinteger(state, r.y);
return 2;
}
int getBaseLocation(lua_State* state){
//Make sure there's only one argument and that argument is an userdatum.
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Get the object.
SDL_Rect r = object->getBox(BoxType_Base);
lua_pushinteger(state, r.x);
lua_pushinteger(state, r.y);
return 2;
}
int setLocation(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number);
HELPER_CHECK_ARGS_TYPE(3, number);
//Now get the pointer to the object.
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
int x = lua_tonumber(state, 2);
int y = lua_tonumber(state, 3);
object->setLocation(x, y);
return 0;
}
int setBaseLocation(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number);
HELPER_CHECK_ARGS_TYPE(3, number);
//Now get the pointer to the object.
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
int x = lua_tonumber(state, 2);
int y = lua_tonumber(state, 3);
object->setBaseLocation(x, y);
return 0;
}
int growTo(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number);
HELPER_CHECK_ARGS_TYPE(3, number);
//Now get the pointer to the object.
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
int w = lua_tonumber(state, 2);
int h = lua_tonumber(state, 3);
object->growTo(w, h);
return 0;
}
int getSize(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Get the object.
lua_pushinteger(state, object->getBox().w);
lua_pushinteger(state, object->getBox().h);
return 2;
}
int getBaseSize(lua_State* state){
//Make sure there's only one argument and that argument is an userdatum.
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Get the object.
SDL_Rect r = object->getBox(BoxType_Base);
lua_pushnumber(state, r.w);
lua_pushnumber(state, r.h);
return 2;
}
int setSize(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number);
HELPER_CHECK_ARGS_TYPE(3, number);
//Now get the pointer to the object.
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
int w = lua_tonumber(state, 2);
int h = lua_tonumber(state, 3);
object->setSize(w, h);
return 0;
}
int setBaseSize(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number);
HELPER_CHECK_ARGS_TYPE(3, number);
//Now get the pointer to the object.
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
int w = lua_tonumber(state, 2);
int h = lua_tonumber(state, 3);
object->setBaseSize(w, h);
return 0;
}
int getType(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL || object->type < 0 || object->type >= TYPE_MAX) return 0;
lua_pushstring(state, Game::blockName[object->type]);
return 1;
}
int changeThemeState(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, string);
Block* object = Block::getObjectFromUserData(state, 1);
object->appearance.changeState(lua_tostring(state, 2));
return 0;
}
int setVisible(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, boolean);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL)
return 0;
BlockScriptAPI::setFlags(object,
(BlockScriptAPI::getFlags(object) & ~0x80000000) | (lua_toboolean(state, 2) ? 0 : 0x80000000)
);
return 0;
}
int isVisible(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL)
return 0;
lua_pushboolean(state, (BlockScriptAPI::getFlags(object) & 0x80000000) ? 0 : 1);
return 1;
}
int getEventHandler(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, string);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Check event type
string eventType = lua_tostring(state, 2);
map<string, int>::iterator it = Game::gameObjectEventNameMap.find(eventType);
if (it == Game::gameObjectEventNameMap.end()) return 0;
//Check compiled script
map<int, int>::iterator script = object->compiledScripts.find(it->second);
if (script == object->compiledScripts.end()) return 0;
//Get event handler
lua_rawgeti(state, LUA_REGISTRYINDEX, script->second);
return 1;
}
//It will return old event handler.
int setEventHandler(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, string);
HELPER_CHECK_ARGS_TYPE_OR_NIL(3, function);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Check event type
string eventType = lua_tostring(state, 2);
map<string, int>::const_iterator it = Game::gameObjectEventNameMap.find(eventType);
if (it == Game::gameObjectEventNameMap.end()){
lua_pushfstring(state, "Unknown block event type: '%s'.", eventType.c_str());
return lua_error(state);
}
//Check compiled script
int scriptIndex = LUA_REFNIL;
{
map<int, int>::iterator script = object->compiledScripts.find(it->second);
if (script != object->compiledScripts.end()) scriptIndex = script->second;
}
//Set new event handler
object->compiledScripts[it->second] = luaL_ref(state, LUA_REGISTRYINDEX);
if (scriptIndex == LUA_REFNIL) return 0;
//Get old event handler and unreference it
lua_rawgeti(state, LUA_REGISTRYINDEX, scriptIndex);
luaL_unref(state, LUA_REGISTRYINDEX, scriptIndex);
return 1;
}
int onEvent(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, string);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Check event type
string eventType = lua_tostring(state, 2);
map<string, int>::const_iterator it = Game::gameObjectEventNameMap.find(eventType);
if (it == Game::gameObjectEventNameMap.end()){
lua_pushfstring(state, "Unknown block event type: '%s'.", eventType.c_str());
return lua_error(state);
}
object->onEvent(it->second);
return 0;
}
int isActivated(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
lua_pushboolean(state, (BlockScriptAPI::getFlags(object) & 0x1) ? 0 : 1);
return 1;
default:
return 0;
}
}
int setActivated(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, boolean);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
BlockScriptAPI::setFlags(object,
(BlockScriptAPI::getFlags(object) & ~1) | (lua_toboolean(state, 2) ? 0 : 1)
);
break;
}
return 0;
}
int isAutomatic(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_PORTAL:
lua_pushboolean(state, (BlockScriptAPI::getFlags(object) & 0x1) ? 1 : 0);
return 1;
default:
return 0;
}
}
int setAutomatic(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, boolean);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_PORTAL:
BlockScriptAPI::setFlags(object,
(BlockScriptAPI::getFlags(object) & ~1) | (lua_toboolean(state, 2) ? 1 : 0)
);
break;
}
return 0;
}
int getBehavior(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_BUTTON:
case TYPE_SWITCH:
switch (BlockScriptAPI::getFlags(object) & 0x3) {
default:
lua_pushstring(state, "toggle");
break;
case 1:
lua_pushstring(state, "on");
break;
case 2:
lua_pushstring(state, "off");
break;
}
return 1;
default:
return 0;
}
}
int setBehavior(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, string);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_BUTTON:
case TYPE_SWITCH:
{
int newFlags = BlockScriptAPI::getFlags(object) & ~3;
std::string s = lua_tostring(state, 2);
if (s == "on") newFlags |= 1;
else if (s == "off") newFlags |= 2;
BlockScriptAPI::setFlags(object, newFlags);
}
break;
}
return 0;
}
int getState(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_FRAGILE:
lua_pushinteger(state, BlockScriptAPI::getFlags(object) & 0x3);
return 1;
default:
return 0;
}
}
int setState(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_FRAGILE:
{
int oldState = BlockScriptAPI::getFlags(object) & 0x3;
int newState = (int)lua_tonumber(state, 2);
if (newState < 0) newState = 0;
else if (newState > 3) newState = 3;
if (newState != oldState) {
BlockScriptAPI::fragileUpdateState(object, newState);
}
}
break;
}
return 0;
}
int isPlayerOn(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_BUTTON:
lua_pushboolean(state, (BlockScriptAPI::getFlags(object) & 0x4) ? 1 : 0);
return 1;
default:
return 0;
}
}
int getPathMaxTime(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
lua_pushinteger(state, object->getPathMaxTime());
return 1;
default:
return 0;
}
}
int getPathTime(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
lua_pushinteger(state, BlockScriptAPI::getTemp(object));
return 1;
default:
return 0;
}
}
int setPathTime(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
BlockScriptAPI::setTemp(object, (int)lua_tonumber(state, 2));
break;
}
return 0;
}
int isLooping(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
lua_pushboolean(state, (BlockScriptAPI::getFlags(object) & 0x2) ? 0 : 1);
return 1;
default:
return 0;
}
}
int setLooping(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, boolean);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
BlockScriptAPI::setFlags(object,
(BlockScriptAPI::getFlags(object) & ~2) | (lua_toboolean(state, 2) ? 0 : 2)
);
break;
}
return 0;
}
int getSpeed(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
lua_pushinteger(state, BlockScriptAPI::getSpeed(object));
return 1;
default:
return 0;
}
}
int setSpeed(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
BlockScriptAPI::setSpeed(object, (int)lua_tonumber(state, 2));
break;
}
return 0;
}
int getAppearance(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
lua_pushstring(state, object->customAppearanceName.c_str());
return 1;
}
int setAppearance(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE_OR_NIL(2, string);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
if (lua_isnil(state, 2)) {
object->setEditorProperty("appearance", "");
} else {
object->setEditorProperty("appearance", lua_tostring(state, 2));
}
return 0;
}
int getId(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
lua_pushstring(state, object->id.c_str());
return 1;
}
int setId(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE_OR_NIL(2, string);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
if (lua_isnil(state, 2)) {
object->id.clear();
} else {
object->id = lua_tostring(state, 2);
}
return 0;
}
int getDestination(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_PORTAL:
lua_pushstring(state, object->destination.c_str());
return 1;
default:
return 0;
}
return 1;
}
int setDestination(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE_OR_NIL(2, string);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_PORTAL:
if (lua_isnil(state, 2)) {
object->destination.clear();
} else {
object->destination = lua_tostring(state, 2);
}
break;
}
return 0;
}
int getMessage(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_NOTIFICATION_BLOCK:
lua_pushstring(state, object->message.c_str());
return 1;
default:
return 0;
}
return 1;
}
int setMessage(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE_OR_NIL(2, string);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
std::string newMessage;
switch (object->type) {
case TYPE_NOTIFICATION_BLOCK:
if (!lua_isnil(state, 2)) {
newMessage = lua_tostring(state, 2);
}
if (newMessage != object->message) {
object->message = newMessage;
//Invalidate the notification texture
if (Game* game = dynamic_cast<Game*>(currentState)) {
game->invalidateNotificationTexture(object);
}
}
break;
}
return 0;
}
int getMovingPosCount(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
lua_pushinteger(state, BlockScriptAPI::getMovingPos(object).size());
return 1;
default:
return 0;
}
}
void _pushAMovingPos(lua_State* state, const SDL_Rect& r) {
lua_createtable(state, 3, 0);
lua_pushinteger(state, r.x);
lua_rawseti(state, -2, 1);
lua_pushinteger(state, r.y);
lua_rawseti(state, -2, 2);
lua_pushinteger(state, r.w);
lua_rawseti(state, -2, 3);
}
int getMovingPos(lua_State* state) {
//Available overloads:
//getMovingPos()
//getMovingPos(index)
//getMovingPos(start, length)
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS_RANGE(1, 3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_OPTIONAL_ARGS_TYPE(2, number);
HELPER_CHECK_OPTIONAL_ARGS_TYPE(3, number);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
break;
default:
return 0;
}
const std::vector<SDL_Rect> &movingPos = BlockScriptAPI::getMovingPos(object);
const int m = movingPos.size();
int start = 0, length = -1;
if (args >= 2) start = lua_tonumber(state, 2) - 1;
if (args >= 3) length = lua_tonumber(state, 3);
//Length<0 means get all of remaining points
if (length < 0) length = m - start;
//Some sanity check
if (start < 0) return 0;
if (start + length > m) length = m - start;
if (length < 0) length = 0;
if (args == 2) {
//Get single point
//Sanity check
if (start >= m) return 0;
_pushAMovingPos(state, movingPos[start]);
} else {
//Get array of points
lua_createtable(state, length, 0);
for (int i = 0; i < length; i++) {
_pushAMovingPos(state, movingPos[start + i]);
lua_rawseti(state, -2, i + 1);
}
}
return 1;
}
SDL_Rect _getAMovingPos(lua_State* state, int index) {
SDL_Rect ret = { 0, 0, 0, 0 };
if (lua_istable(state, index) && lua_rawlen(state, index) >= 3) {
lua_rawgeti(state, index, 1);
ret.x = lua_tonumber(state, -1);
lua_pop(state, 1);
lua_rawgeti(state, index, 2);
ret.y = lua_tonumber(state, -1);
lua_pop(state, 1);
lua_rawgeti(state, index, 3);
ret.w = lua_tonumber(state, -1);
lua_pop(state, 1);
}
return ret;
}
void _getArrayOfMovingPos(lua_State* state, int index, std::vector<SDL_Rect>& ret, int maxLength = -1) {
if (lua_istable(state, index)) {
int m = lua_rawlen(state, index);
if (maxLength >= 0 && m > maxLength) m = maxLength;
for (int i = 0; i < m; i++) {
lua_rawgeti(state, index, i + 1);
ret.push_back(_getAMovingPos(state, -1));
lua_pop(state, 1);
}
}
}
int setMovingPos(lua_State* state) {
//Available overloads:
//setMovingPos(array)
//setMovingPos(index, point)
//setMovingPos(start, length, array)
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS_RANGE(2, 4);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
for (int i = 2; i < args; i++) {
HELPER_CHECK_ARGS_TYPE(i, number);
}
HELPER_CHECK_ARGS_TYPE(args, table);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
break;
default:
return 0;
}
std::vector<SDL_Rect> &movingPos = BlockScriptAPI::getMovingPos(object);
if (args == 2) {
//Overwrite the whole array
movingPos.clear();
_getArrayOfMovingPos(state, args, movingPos);
BlockScriptAPI::invalidatePathMaxTime(object);
return 0;
}
const int m = movingPos.size();
int start = 0, length = -1;
if (args >= 3) start = lua_tonumber(state, 2) - 1;
if (args >= 4) length = lua_tonumber(state, 3);
//Length<0 means set all of remaining points
if (length < 0) length = m - start;
//Some sanity check
if (start < 0) return 0;
if (start + length > m) length = m - start;
if (length < 0) length = 0;
if (args == 3) {
//Set single point
//Sanity check
if (start >= m) return 0;
movingPos[start] = _getAMovingPos(state, args);
BlockScriptAPI::invalidatePathMaxTime(object);
} else if (length > 0) {
//Set array of points
std::vector<SDL_Rect> newPos;
_getArrayOfMovingPos(state, args, newPos, length);
length = newPos.size();
for (int i = 0; i < length; i++) {
movingPos[start + i] = newPos[i];
}
if (length > 0) {
BlockScriptAPI::invalidatePathMaxTime(object);
}
}
return 0;
}
int addMovingPos(lua_State* state) {
//Available overloads:
//addMovingPos(p)
//addMovingPos(index, p)
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS_RANGE(2, 3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
for (int i = 2; i < args; i++) {
HELPER_CHECK_ARGS_TYPE(i, number);
}
HELPER_CHECK_ARGS_TYPE(args, table);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
break;
default:
return 0;
}
std::vector<SDL_Rect> &movingPos = BlockScriptAPI::getMovingPos(object);
const int m = movingPos.size();
int start = m;
if (args >= 3) start = lua_tonumber(state, 2) - 1;
//Some sanity check
if (start < 0) start = 0;
if (start > m) start = m;
//Get the list of points
std::vector<SDL_Rect> newPos;
bool singlePoint = false;
if (lua_istable(state, args) && lua_rawlen(state, args) >= 3) {
lua_rawgeti(state, args, 1);
lua_rawgeti(state, args, 2);
lua_rawgeti(state, args, 3);
if (lua_isnumber(state, -3) && lua_isnumber(state, -2) && lua_isnumber(state, -1)) {
newPos.push_back(SDL_Rect{
lua_tonumber(state, -3),
lua_tonumber(state, -2),
lua_tonumber(state, -1),
0
});
singlePoint = true;
}
lua_pop(state, 3);
}
if (!singlePoint) {
_getArrayOfMovingPos(state, args, newPos);
}
if (!newPos.empty()) {
movingPos.insert(movingPos.begin() + start, newPos.begin(), newPos.end());
BlockScriptAPI::invalidatePathMaxTime(object);
}
return 0;
}
void _getArrayOfInteger(lua_State* state, int index, std::vector<int>& ret) {
if (lua_istable(state, index)) {
int m = lua_rawlen(state, index);
for (int i = 0; i < m; i++) {
lua_rawgeti(state, index, i + 1);
if (lua_isnumber(state, -1)) {
ret.push_back(lua_tonumber(state, -1));
}
lua_pop(state, 1);
}
}
}
int removeMovingPos(lua_State* state) {
//Available overloads:
//removeMovingPos()
//removeMovingPos(index)
//removeMovingPos(listOfIndices)
//removeMovingPos(start, length)
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS_RANGE(1, 3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
switch (args) {
case 2:
HELPER_CHECK_ARGS_TYPE_2(2, number, table);
break;
case 3:
HELPER_CHECK_ARGS_TYPE(2, number);
HELPER_CHECK_ARGS_TYPE(3, number);
break;
}
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
break;
default:
return 0;
}
std::vector<SDL_Rect> &movingPos = BlockScriptAPI::getMovingPos(object);
if (args == 1) {
movingPos.clear();
BlockScriptAPI::invalidatePathMaxTime(object);
return 0;
}
const int m = movingPos.size();
if (args == 3) {
int start = lua_tonumber(state, 2) - 1;
int length = lua_tonumber(state, 3);
//Length<0 means remove all of remaining points
if (length < 0) length = m - start;
//Some sanity check
if (start < 0 || start >= m) return 0;
if (start + length > m) length = m - start;
if (length < 0) length = 0;
if (length > 0) {
movingPos.erase(movingPos.begin() + start, movingPos.begin() + (start + length));
BlockScriptAPI::invalidatePathMaxTime(object);
}
return 0;
}
if (lua_isnumber(state, 2)) {
int start = lua_tonumber(state, 2) - 1;
//Some sanity check
if (start < 0 || start >= m) return 0;
movingPos.erase(movingPos.begin() + start);
BlockScriptAPI::invalidatePathMaxTime(object);
return 0;
}
std::vector<int> indices;
_getArrayOfInteger(state, 2, indices);
std::sort(indices.begin(), indices.end());
int i2 = 0, j = 0;
const int m2 = indices.size();
for (int i = 0; i < m; i++) {
// find the first index which is >= current
while (i2 < m2 && indices[i2] < i + 1) i2++;
if (i2 < m2 && indices[i2] == i + 1) {
// this point will be removed
j++;
} else {
// this point is preserved
if (j > 0) {
movingPos[i - j] = movingPos[i];
}
}
}
if (j > 0) {
movingPos.resize(m - j);
BlockScriptAPI::invalidatePathMaxTime(object);
}
return 0;
}
int remove(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
object->deleteMe();
return 0;
}
int removeAll(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(0);
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
for (auto o : game->levelObjects) {
if (o) o->deleteMe();
}
return 0;
}
int addBlock(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS_RANGE(1, 5);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string);
for (int i = 2; i <= args; i++) {
HELPER_CHECK_ARGS_TYPE(i, number);
}
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
TreeStorageNode root;
//Load from the string.
{
POASerializer objSerializer;
istringstream stream(lua_tostring(state, 1));
if (!objSerializer.readNode(stream, &root, true)) {
return luaL_error(state, "Failed to load node from string in %s", __FUNCTION__);
}
}
//Load the first valid block in the subnodes.
for (auto obj1 : root.subNodes) {
if (obj1 == NULL) continue;
if (obj1->name == "tile"){
Block* block = new Block(game);
if (!block->loadFromNode(getImageManager(), getRenderer(), obj1)) {
delete block;
continue;
}
//Reposition the block if necessary
SDL_Rect r = block->getBox(BoxType_Base);
if (args >= 2) {
r.x = lua_tonumber(state, 2);
if (args >= 3) r.y = lua_tonumber(state, 3);
block->setBaseLocation(r.x, r.y);
}
if (args >= 4) {
r.w = lua_tonumber(state, 4);
if (args >= 5) r.h = lua_tonumber(state, 5);
block->setBaseSize(r.w, r.h);
}
//If the type is collectable, increase the number of totalCollectables
if (block->type == TYPE_COLLECTABLE) {
game->totalCollectables++;
if (BlockScriptAPI::getFlags(block) & 0x1) game->currentCollectables++;
}
//Add the block to the levelObjects vector.
game->levelObjects.push_back(block);
//Enable the access to this block from script.
block->setActive();
//Compile the block script.
for (auto it = block->scripts.begin(); it != block->scripts.end(); ++it){
int index = game->getScriptExecutor()->compileScript(it->second);
block->compiledScripts[it->first] = index;
}
//Trigger the onCreate event.
block->onEvent(GameObjectEvent_OnCreate);
//Return the newly created block.
block->createUserData(state, "block");
return 1;
}
}
return 0;
}
SDL_Rect _getAnSDLRect(lua_State* state, int index) {
SDL_Rect ret = { 0x80000000, 0x80000000, 0x80000000, 0x80000000 };
if (lua_istable(state, index)) {
const int m = lua_rawlen(state, index);
if (m >= 1) {
lua_rawgeti(state, index, 1);
int n, b; n = lua_tonumberx(state, -1, &b);
if (b) ret.x = n;
lua_pop(state, 1);
}
if (m >= 2) {
lua_rawgeti(state, index, 2);
int n, b; n = lua_tonumberx(state, -1, &b);
if (b) ret.y = n;
lua_pop(state, 1);
}
if (m >= 3) {
lua_rawgeti(state, index, 3);
int n, b; n = lua_tonumberx(state, -1, &b);
if (b) ret.w = n;
lua_pop(state, 1);
}
if (m >= 4) {
lua_rawgeti(state, index, 4);
int n, b; n = lua_tonumberx(state, -1, &b);
if (b) ret.h = n;
lua_pop(state, 1);
}
}
return ret;
}
void _getArrayOfSDLRect(lua_State* state, int index, std::vector<SDL_Rect>& ret) {
if (lua_istable(state, index)) {
int m = lua_rawlen(state, index);
for (int i = 0; i < m; i++) {
lua_rawgeti(state, index, i + 1);
ret.push_back(_getAnSDLRect(state, -1));
lua_pop(state, 1);
}
}
}
int addBlocks(lua_State* state) {
//Available overloads:
//addBlocks(string)
//addBlocks(string,positions)
//addBlocks(string,offsetX,offsetY)
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS_RANGE(1, 3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string);
switch (args) {
case 2:
HELPER_CHECK_ARGS_TYPE(2, table);
break;
case 3:
HELPER_CHECK_ARGS_TYPE(2, number);
HELPER_CHECK_ARGS_TYPE(3, number);
break;
}
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
TreeStorageNode root;
//Load from the string.
{
POASerializer objSerializer;
istringstream stream(lua_tostring(state, 1));
if (!objSerializer.readNode(stream, &root, true)) {
return luaL_error(state, "Failed to load node from string in %s", __FUNCTION__);
}
}
std::vector<TreeStorageNode*> blockNodes;
//Get available blocks.
for (auto obj1 : root.subNodes) {
if (obj1 == NULL) continue;
if (obj1->name == "tile") blockNodes.push_back(obj1);
}
std::vector<SDL_Rect> positions;
std::vector<Block*> blocks;
int offsetX = 0, offsetY = 0;
//Check if we should get positions.
if (args == 2) {
_getArrayOfSDLRect(state, 2, positions);
//Check if we should load block repeatedly.
if (blockNodes.size() == 1 && positions.size() >= 1) {
Block* blockTemplate = new Block(game);
if (!blockTemplate->loadFromNode(getImageManager(), getRenderer(), blockNodes[0])) {
delete blockTemplate;
//Just return an empty array.
lua_createtable(state, 0, 0);
return 1;
}
//Compile the block script.
for (auto it = blockTemplate->scripts.begin(); it != blockTemplate->scripts.end(); ++it){
int index = game->getScriptExecutor()->compileScript(it->second);
blockTemplate->compiledScripts[it->first] = index;
}
for (int i = 0, m = positions.size(); i < m; i++) {
Block *block;
if (i < m - 1) {
block = new Block(*blockTemplate);
//Ad-hoc code to make the block proxy unique
block->proxy.reset(new Block::Proxy);
} else {
block = blockTemplate;
}
//Reposition the block if necessary
SDL_Rect r = block->getBox(BoxType_Base);
SDL_Rect r1 = positions[i];
if (r1.x != 0x80000000 || r1.y != 0x80000000) {
if (r1.x != 0x80000000) r.x = r1.x;
if (r1.y != 0x80000000) r.y = r1.y;
block->setBaseLocation(r.x, r.y);
}
if (r1.w != 0x80000000 || r1.h != 0x80000000) {
if (r1.w != 0x80000000) r.w = r1.w;
if (r1.h != 0x80000000) r.h = r1.h;
block->setBaseSize(r.w, r.h);
}
//Add it to the temp array
blocks.push_back(block);
}
}
}
//Check if we should use offsets.
if (args == 3) {
offsetX = lua_tonumber(state, 2);
offsetY = lua_tonumber(state, 3);
}
//Check if we should load block in a regular way.
if (blocks.empty()) {
for (int i = 0, m = blockNodes.size(); i < m; i++) {
Block* block = new Block(game);
if (!block->loadFromNode(getImageManager(), getRenderer(), blockNodes[i])) {
delete block;
continue;
}
//Reposition the block if necessary
SDL_Rect r = block->getBox(BoxType_Base);
SDL_Rect r1 = (args == 3) ? SDL_Rect{ r.x + offsetX, r.y + offsetY, 0x80000000, 0x80000000 } :
(i < (int)positions.size()) ? positions[i] : SDL_Rect{ 0x80000000, 0x80000000, 0x80000000, 0x80000000 };
if (r1.x != 0x80000000 || r1.y != 0x80000000) {
if (r1.x != 0x80000000) r.x = r1.x;
if (r1.y != 0x80000000) r.y = r1.y;
block->setBaseLocation(r.x, r.y);
}
if (r1.w != 0x80000000 || r1.h != 0x80000000) {
if (r1.w != 0x80000000) r.w = r1.w;
if (r1.h != 0x80000000) r.h = r1.h;
block->setBaseSize(r.w, r.h);
}
//Compile the block script.
for (auto it = block->scripts.begin(); it != block->scripts.end(); ++it){
int index = game->getScriptExecutor()->compileScript(it->second);
block->compiledScripts[it->first] = index;
}
//Add it to the temp array
blocks.push_back(block);
}
}
for (auto block : blocks) {
//If the type is collectable, increase the number of totalCollectables
if (block->type == TYPE_COLLECTABLE) {
game->totalCollectables++;
if (BlockScriptAPI::getFlags(block) & 0x1) game->currentCollectables++;
}
//Add the block to the levelObjects vector.
game->levelObjects.push_back(block);
//Enable the access to this block from script.
block->setActive();
//Trigger the onCreate event.
block->onEvent(GameObjectEvent_OnCreate);
}
lua_createtable(state, blocks.size(), 0);
for (int i = 0, m = blocks.size(); i < m; i++) {
blocks[i]->createUserData(state, "block");
lua_rawseti(state, -2, i + 1);
}
return 1;
}
int clone(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS_RANGE(1, 5);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
for (int i = 2; i <= args; i++) {
HELPER_CHECK_ARGS_TYPE(i, number);
}
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
Block* block = new Block(*object);
//Ad-hoc code to make the block proxy unique
block->proxy.reset(new Block::Proxy);
//Reposition the block if necessary
SDL_Rect r = block->getBox(BoxType_Base);
if (args >= 2) {
r.x = lua_tonumber(state, 2);
if (args >= 3) r.y = lua_tonumber(state, 3);
block->setBaseLocation(r.x, r.y);
}
if (args >= 4) {
r.w = lua_tonumber(state, 4);
if (args >= 5) r.h = lua_tonumber(state, 5);
block->setBaseSize(r.w, r.h);
}
//If the type is collectable, increase the number of totalCollectables
if (block->type == TYPE_COLLECTABLE) {
game->totalCollectables++;
if (BlockScriptAPI::getFlags(block) & 0x1) game->currentCollectables++;
}
//Add the block to the levelObjects vector.
game->levelObjects.push_back(block);
//Enable the access to this block from script.
block->setActive();
//Trigger the onCreate event.
block->onEvent(GameObjectEvent_OnCreate);
//Return the newly created block.
block->createUserData(state, "block");
return 1;
}
int cloneMultiple(lua_State* state) {
//Available overloads:
//cloneMultiple(number)
//cloneMultiple(positions)
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE_2(2, number, table);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
std::vector<SDL_Rect> positions;
std::vector<Block*> blocks;
if (lua_isnumber(state, 2)) {
int m = lua_tonumber(state, 2);
if (m > 0) positions.resize(m, SDL_Rect{ 0x80000000, 0x80000000, 0x80000000, 0x80000000 });
} else {
_getArrayOfSDLRect(state, 2, positions);
}
for (int i = 0, m = positions.size(); i < m; i++) {
Block *block = new Block(*object);
//Ad-hoc code to make the block proxy unique
block->proxy.reset(new Block::Proxy);
//Reposition the block if necessary
SDL_Rect r = block->getBox(BoxType_Base);
SDL_Rect r1 = positions[i];
if (r1.x != 0x80000000 || r1.y != 0x80000000) {
if (r1.x != 0x80000000) r.x = r1.x;
if (r1.y != 0x80000000) r.y = r1.y;
block->setBaseLocation(r.x, r.y);
}
if (r1.w != 0x80000000 || r1.h != 0x80000000) {
if (r1.w != 0x80000000) r.w = r1.w;
if (r1.h != 0x80000000) r.h = r1.h;
block->setBaseSize(r.w, r.h);
}
//Add it to the temp array
blocks.push_back(block);
}
for (auto block : blocks) {
//If the type is collectable, increase the number of totalCollectables
if (block->type == TYPE_COLLECTABLE) {
game->totalCollectables++;
if (BlockScriptAPI::getFlags(block) & 0x1) game->currentCollectables++;
}
//Add the block to the levelObjects vector.
game->levelObjects.push_back(block);
//Enable the access to this block from script.
block->setActive();
//Trigger the onCreate event.
block->onEvent(GameObjectEvent_OnCreate);
}
lua_createtable(state, blocks.size(), 0);
for (int i = 0, m = blocks.size(); i < m; i++) {
blocks[i]->createUserData(state, "block");
lua_rawseti(state, -2, i + 1);
}
return 1;
}
}
#define _L block
//Array with the methods for the block library.
static const luaL_Reg blocklib_m[]={
_FI(Valid),
_FG(BlockById),
_FG(BlocksById),
_F(moveTo),
_FGS(Location),
_FGS(BaseLocation),
_F(growTo),
_FGS(Size),
_FGS(BaseSize),
_FG(Type),
_F(changeThemeState),
_FIS(Visible),
_FGS(EventHandler),
_F(onEvent),
_FIS(Activated),
_FIS(Automatic),
_FGS(Behavior),
_FGS(State),
_FI(PlayerOn),
_FG(PathMaxTime),
_FGS(PathTime),
_FIS(Looping),
_FGS(Speed),
_FGS(Appearance),
_FGS(Id),
_FGS(Destination),
_FGS(Message),
_FG(MovingPosCount),
_FGS(MovingPos),
_F(addMovingPos),
_F(removeMovingPos),
_F(remove),
_F(removeAll),
_F(addBlock),
_F(addBlocks),
_F(clone),
_F(cloneMultiple),
{ NULL, NULL }
};
#undef _L
int luaopen_block(lua_State* state){
luaL_newlib(state,blocklib_m);
//Create the metatable for the block userdata.
luaL_newmetatable(state,"block");
lua_pushstring(state,"__index");
lua_pushvalue(state,-2);
lua_settable(state,-3);
Block::registerMetatableFunctions(state,-3);
//Register the functions and methods.
luaL_setfuncs(state,blocklib_m,0);
return 1;
}
//////////////////////////PLAYER SPECIFIC///////////////////////////
class PlayerScriptAPI {
public:
static bool isInAir(Player* player) {
return player->inAir;
}
static bool canMode(Player* player) {
return player->canMove;
}
static bool isDead(Player* player) {
return player->dead;
}
static bool isHoldingOther(Player* player) {
return player->holdingOther;
}
};
struct PlayerUserDatum{
char sig1,sig2,sig3,sig4;
};
Player* getPlayerFromUserData(lua_State* state,int idx){
PlayerUserDatum* ud=(PlayerUserDatum*)lua_touserdata(state,1);
//Make sure the user datum isn't null.
if(!ud) return NULL;
//Get the game state.
Game* game=dynamic_cast<Game*>(currentState);
if(game==NULL) return NULL;
Player* player=NULL;
//Check the signature to see if it's the player or the shadow.
if(ud->sig1=='P' && ud->sig2=='L' && ud->sig3=='Y' && ud->sig4=='R')
player=&game->player;
else if(ud->sig1=='S' && ud->sig2=='H' && ud->sig3=='D' && ud->sig4=='W')
player=&game->shadow;
return player;
}
namespace playershadow {
int getLocation(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
//Get the object.
lua_pushinteger(state, player->getBox().x);
lua_pushinteger(state, player->getBox().y);
return 2;
}
int setLocation(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number);
HELPER_CHECK_ARGS_TYPE(3, number);
//Get the player.
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
//Get the new location.
int x = lua_tonumber(state, 2);
int y = lua_tonumber(state, 3);
player->setLocation(x, y);
return 0;
}
int jump(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS_2(1, 2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_OPTIONAL_ARGS_TYPE(2, number);
//Get the player.
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
//Get the new location.
if (args == 2){
int yVel = lua_tonumber(state, 2);
player->jump(yVel);
} else{
//Use default jump strength.
player->jump();
}
return 0;
}
int isShadow(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
lua_pushboolean(state, player->isShadow());
return 1;
}
int getCurrentStand(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
//Get the actual game object.
Block* object = player->getObjCurrentStand();
if (object == NULL){
return 0;
}
//Create the userdatum.
object->createUserData(state, "block");
//We return one object, the userdatum.
return 1;
}
int isInAir(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
lua_pushboolean(state, PlayerScriptAPI::isInAir(player));
return 1;
}
int canMove(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
lua_pushboolean(state, PlayerScriptAPI::canMode(player));
return 1;
}
int isDead(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
lua_pushboolean(state, PlayerScriptAPI::isDead(player));
return 1;
}
int isHoldingOther(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
lua_pushboolean(state, PlayerScriptAPI::isHoldingOther(player));
return 1;
}
}
#define _L playershadow
//Array with the methods for the player and shadow library.
static const luaL_Reg playerlib_m[]={
_FGS(Location),
_F(jump),
_FI(Shadow),
_FG(CurrentStand),
_FI(InAir),
_F(canMove),
_FI(Dead),
_FI(HoldingOther),
{ NULL, NULL }
};
#undef _L
int luaopen_player(lua_State* state){
luaL_newlib(state,playerlib_m);
//Create the metatable for the player userdata.
luaL_newmetatable(state,"player");
lua_pushstring(state,"__index");
lua_pushvalue(state,-2);
lua_settable(state,-3);
//Now create two default player user data, one for the player and one for the shadow.
PlayerUserDatum* ud=(PlayerUserDatum*)lua_newuserdata(state,sizeof(PlayerUserDatum));
ud->sig1='P';ud->sig2='L';ud->sig3='Y';ud->sig4='R';
luaL_getmetatable(state,"player");
lua_setmetatable(state,-2);
lua_setglobal(state,"player");
ud=(PlayerUserDatum*)lua_newuserdata(state,sizeof(PlayerUserDatum));
ud->sig1='S';ud->sig2='H';ud->sig3='D';ud->sig4='W';
luaL_getmetatable(state,"player");
lua_setmetatable(state,-2);
lua_setglobal(state,"shadow");
//Register the functions and methods.
luaL_setfuncs(state,playerlib_m,0);
return 1;
}
//////////////////////////LEVEL SPECIFIC///////////////////////////
namespace level {
int getSize(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Returns level size.
lua_pushinteger(state, game->levelRect.w);
lua_pushinteger(state, game->levelRect.h);
return 2;
}
int getRect(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Returns level size.
lua_pushinteger(state, game->levelRect.x);
lua_pushinteger(state, game->levelRect.y);
lua_pushinteger(state, game->levelRect.w);
lua_pushinteger(state, game->levelRect.h);
return 4;
}
int setRect(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(4);
//Check if the arguments are of the right type.
for (int i = 1; i <= args; i++) {
HELPER_CHECK_ARGS_TYPE(i, number);
}
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Set the level size.
game->levelRect = SDL_Rect {
(int)lua_tonumber(state, 1),
(int)lua_tonumber(state, 2),
(int)lua_tonumber(state, 3),
(int)lua_tonumber(state, 4)
};
return 0;
}
int getWidth(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Returns level size.
lua_pushinteger(state, game->levelRect.w);
return 1;
}
int getHeight(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Returns level size.
lua_pushinteger(state, game->levelRect.h);
return 1;
}
int getName(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Returns level name.
lua_pushstring(state, game->getLevelName().c_str());
return 1;
}
int getEventHandler(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string);
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Check event type
string eventType = lua_tostring(state, 1);
map<string, int>::iterator it = Game::levelEventNameMap.find(eventType);
if (it == Game::levelEventNameMap.end()) return 0;
//Check compiled script
map<int, int>::iterator script = game->compiledScripts.find(it->second);
if (script == game->compiledScripts.end()) return 0;
//Get event handler
lua_rawgeti(state, LUA_REGISTRYINDEX, script->second);
return 1;
}
//It will return old event handler.
int setEventHandler(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string);
HELPER_CHECK_ARGS_TYPE_OR_NIL(2, function);
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Check event type
string eventType = lua_tostring(state, 1);
map<string, int>::const_iterator it = Game::levelEventNameMap.find(eventType);
if (it == Game::levelEventNameMap.end()){
lua_pushfstring(state, "Unknown level event type: '%s'.", eventType.c_str());
return lua_error(state);
}
//Check compiled script
int scriptIndex = LUA_REFNIL;
{
map<int, int>::iterator script = game->compiledScripts.find(it->second);
if (script != game->compiledScripts.end()) scriptIndex = script->second;
}
//Set new event handler
game->compiledScripts[it->second] = luaL_ref(state, LUA_REGISTRYINDEX);
if (scriptIndex == LUA_REFNIL) return 0;
//Get old event handler and unreference it
lua_rawgeti(state, LUA_REGISTRYINDEX, scriptIndex);
luaL_unref(state, LUA_REGISTRYINDEX, scriptIndex);
return 1;
}
int win(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Check if the currentState is the game state.
if (stateID == STATE_LEVEL_EDITOR)
return 0;
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
game->won = true;
return 0;
}
int getTime(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Returns level size.
lua_pushinteger(state, game->time);
return 1;
}
int getRecordings(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Returns level size.
lua_pushinteger(state, game->recordings);
return 1;
}
int broadcastObjectEvent(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS_RANGE(1, 4);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string);
HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL(2, string);
HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL(3, string);
HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL_NO_HINT(4, userdata);
//Check event type
int eventType = 0;
{
string s = lua_tostring(state, 1);
auto it = Game::gameObjectEventNameMap.find(s);
if (it == Game::gameObjectEventNameMap.end()){
lua_pushfstring(state, "Unknown block event type: '%s'.", s.c_str());
return lua_error(state);
} else {
eventType = it->second;
}
}
//Check object type
int objType = -1;
if (args >= 2 && lua_isstring(state, 2)) {
string s = lua_tostring(state, 2);
auto it = Game::blockNameMap.find(s);
if (it == Game::blockNameMap.end()){
lua_pushfstring(state, "Unknown object type: '%s'.", s.c_str());
return lua_error(state);
} else {
objType = it->second;
}
}
//Check id
const char* id = NULL;
if (args >= 3 && lua_isstring(state, 3)) {
id = lua_tostring(state, 3);
}
//Check target
Block *target = NULL;
if (args >= 4) {
target = Block::getObjectFromUserData(state, 4);
}
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
game->broadcastObjectEvent(eventType, objType, id, target);
return 0;
}
}
#define _L level
//Array with the methods for the level library.
static const luaL_Reg levellib_m[]={
_FG(Size),
_FGS(Rect),
_FG(Width),
_FG(Height),
_FG(Name),
_FGS(EventHandler),
_F(win),
_FG(Time),
_FG(Recordings),
_F(broadcastObjectEvent),
{NULL,NULL}
};
#undef _L
int luaopen_level(lua_State* state){
luaL_newlib(state,levellib_m);
//Register the functions and methods.
luaL_setfuncs(state,levellib_m,0);
return 1;
}
/////////////////////////CAMERA SPECIFIC///////////////////////////
//FIXME: I can't define namespace camera since there is already a global variable named camera.
//Therefore I use struct camera for a workaround.
struct camera {
static int setMode(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string);
string mode = lua_tostring(state, 1);
//Get the game for setting the camera.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Check which mode.
if (mode == "player"){
game->cameraMode = Game::CAMERA_PLAYER;
} else if (mode == "shadow"){
game->cameraMode = Game::CAMERA_SHADOW;
} else{
//Unknown OR invalid camera mode.
return luaL_error(state, "Unknown or invalid camera mode for %s: '%s'.", __FUNCTION__, mode.c_str());
}
//Returns nothing.
return 0;
}
static int lookAt(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, number);
HELPER_CHECK_ARGS_TYPE(2, number);
//Get the point.
int x = lua_tonumber(state, 1);
int y = lua_tonumber(state, 2);
//Get the game for setting the camera.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
game->cameraMode = Game::CAMERA_CUSTOM;
game->cameraTarget.x = x;
game->cameraTarget.y = y;
return 0;
}
};
#define _L camera
//Array with the methods for the camera library.
static const luaL_Reg cameralib_m[]={
_FS(Mode),
_F(lookAt),
{NULL,NULL}
};
#undef _L
int luaopen_camera(lua_State* state){
luaL_newlib(state,cameralib_m);
//Register the functions and methods.
luaL_setfuncs(state,cameralib_m,0);
return 1;
}
/////////////////////////AUDIO SPECIFIC///////////////////////////
namespace audio {
int playSound(lua_State* state){
//Get the number of args, this can be anything from one to three.
HELPER_GET_AND_CHECK_ARGS_RANGE(1, 4);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string);
HELPER_CHECK_OPTIONAL_ARGS_TYPE(2, number);
HELPER_CHECK_OPTIONAL_ARGS_TYPE(3, boolean);
HELPER_CHECK_OPTIONAL_ARGS_TYPE(4, number);
//Default values for concurrent and force.
//See SoundManager.h
int concurrent = -1;
bool force = false;
int fadeMusic = -1;
//If there's a second one it should be an integer.
if (args > 1){
concurrent = lua_tonumber(state, 2);
}
//If there's a third one it should be a boolean.
if (args > 2){
force = lua_toboolean(state, 3);
}
if (args > 3){
fadeMusic = lua_tonumber(state, 4);
}
//Get the name of the sound.
string sound = lua_tostring(state, 1);
//Try to play the sound.
int channel = getSoundManager()->playSound(sound, concurrent, force, fadeMusic);
//Returns whether the operation is successful.
lua_pushboolean(state, channel >= 0 ? 1 : 0);
return 1;
}
int playMusic(lua_State* state){
//Get the number of args, this can be either one or two.
HELPER_GET_AND_CHECK_ARGS_2(1, 2);
//Make sure the first argument is a string.
HELPER_CHECK_ARGS_TYPE(1, string);
HELPER_CHECK_OPTIONAL_ARGS_TYPE(2, boolean);
//Default value of fade for playMusic.
//See MusicManager.h.
bool fade = true;
//If there's a second one it should be a boolean.
if (args > 1){
fade = lua_toboolean(state, 2);
}
//Get the name of the music.
string music = lua_tostring(state, 1);
//Try to switch to the new music.
getMusicManager()->playMusic(music, fade);
//Returns nothing.
return 0;
}
int pickMusic(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Let the music manager pick a song from the current music list.
getMusicManager()->pickMusic();
return 0;
}
int setMusicList(lua_State* state){
//Get the number of args, this MUST be one.
HELPER_GET_AND_CHECK_ARGS(1);
//Make sure the given argument is a string.
HELPER_CHECK_ARGS_TYPE(1, string);
//And set the music list in the music manager.
string list = lua_tostring(state, 1);
getMusicManager()->setMusicList(list);
return 0;
}
int getMusicList(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Return the name of the song (contains list prefix).
lua_pushstring(state, getMusicManager()->getCurrentMusicList().c_str());
return 1;
}
int currentMusic(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Return the name of the song (contains list prefix).
lua_pushstring(state, getMusicManager()->getCurrentMusic().c_str());
return 1;
}
}
#define _L audio
//Array with the methods for the audio library.
static const luaL_Reg audiolib_m[]={
_F(playSound),
_F(playMusic),
_F(pickMusic),
_FGS(MusicList),
_F(currentMusic),
{NULL,NULL}
};
#undef _L
int luaopen_audio(lua_State* state){
luaL_newlib(state,audiolib_m);
//Register the functions and methods.
luaL_setfuncs(state,audiolib_m,0);
return 1;
}
/////////////////////////DELAY EXECUTION SPECIFIC///////////////////////////
namespace delayExecution {
HELPER_REGISTER_IS_VALID_FUNCTION(ScriptDelayExecution);
int schedule(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS_AT_LEAST(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_OR_NIL(1, function);
HELPER_CHECK_ARGS_TYPE(2, number);
HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL(3, number);
HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL(4, number);
HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL(5, boolean);
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Create the delay execution object.
ScriptDelayExecution *obj = new ScriptDelayExecution(game->getScriptExecutor()->getDelayExecutionList());
obj->setActive();
obj->time = (int)lua_tonumber(state, 2);
obj->repeatCount = (args >= 3 && lua_isnumber(state, 3)) ? (int)lua_tonumber(state, 3) : 1;
obj->repeatInterval = (args >= 4 && lua_isnumber(state, 4)) ? (int)lua_tonumber(state, 4) : obj->time;
obj->enabled = ((args >= 5 && lua_isboolean(state, 5)) ? lua_toboolean(state, 5) : 1) != 0;
//Get arguments.
for (int i = 6; i <= args; i++) {
obj->arguments.push_back(luaL_ref(state, LUA_REGISTRYINDEX));
}
std::reverse(obj->arguments.begin(), obj->arguments.end());
//Get the function.
lua_settop(state, 1);
obj->func = luaL_ref(state, LUA_REGISTRYINDEX);
//Create the userdatum.
obj->createUserData(state, "delayExecution");
//We return one object, the userdatum.
return 1;
}
int cancel(lua_State* state){
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Delete the object.
delete object;
return 0;
}
int isEnabled(lua_State* state){
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
lua_pushboolean(state, object->enabled ? 1 : 0);
return 1;
}
int setEnabled(lua_State* state) {
HELPER_GET_AND_CHECK_ARGS(2);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, boolean);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
object->enabled = lua_toboolean(state, 2) != 0;
return 0;
}
int getTime(lua_State* state){
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
lua_pushinteger(state, object->time);
return 1;
}
int setTime(lua_State* state) {
HELPER_GET_AND_CHECK_ARGS(2);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
object->time = (int)lua_tonumber(state, 2);
return 0;
}
int getRepeatCount(lua_State* state){
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
lua_pushinteger(state, object->repeatCount);
return 1;
}
int setRepeatCount(lua_State* state) {
HELPER_GET_AND_CHECK_ARGS(2);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
object->repeatCount = (int)lua_tonumber(state, 2);
return 0;
}
int getRepeatInterval(lua_State* state){
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
lua_pushinteger(state, object->repeatInterval);
return 1;
}
int setRepeatInterval(lua_State* state) {
HELPER_GET_AND_CHECK_ARGS(2);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Set the repeat interval (should >=1).
int i = (int)lua_tonumber(state, 2);
if (i > 0) object->repeatInterval = i;
return 0;
}
int getFunc(lua_State* state){
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
if (object->func == LUA_REFNIL) return 0;
lua_rawgeti(state, LUA_REGISTRYINDEX, object->func);
return 1;
}
int setFunc(lua_State* state) {
HELPER_GET_AND_CHECK_ARGS(2);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE_OR_NIL(2, function);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
int oldFunc = object->func;
object->func = luaL_ref(state, LUA_REGISTRYINDEX);
if (oldFunc == LUA_REFNIL) return 0;
lua_rawgeti(state, LUA_REGISTRYINDEX, oldFunc);
luaL_unref(state, LUA_REGISTRYINDEX, oldFunc);
return 1;
}
int getArguments(lua_State* state){
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
for (int a : object->arguments) {
lua_rawgeti(state, LUA_REGISTRYINDEX, a);
}
return object->arguments.size();
}
int setArguments(lua_State* state) {
HELPER_GET_AND_CHECK_ARGS_AT_LEAST(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Remove old arguments.
for (int a : object->arguments) {
luaL_unref(state, LUA_REGISTRYINDEX, a);
}
object->arguments.clear();
//Get arguments.
for (int i = 2; i <= args; i++) {
object->arguments.push_back(luaL_ref(state, LUA_REGISTRYINDEX));
}
std::reverse(object->arguments.begin(), object->arguments.end());
return 0;
}
int getExecutionTime(lua_State* state){
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
lua_pushinteger(state, object->executionTime);
return 1;
}
int setExecutionTime(lua_State* state) {
HELPER_GET_AND_CHECK_ARGS(2);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
object->executionTime = (int)lua_tonumber(state, 2);
return 0;
}
}
#define _L delayExecution
//Array with the methods for the block library.
static const luaL_Reg delayExecutionLib_m[] = {
_FI(Valid),
_F(schedule),
_F(cancel),
_FIS(Enabled),
_FGS(Time),
_FGS(RepeatCount),
_FGS(RepeatInterval),
_FGS(Func),
_FGS(Arguments),
_FGS(ExecutionTime),
{ NULL, NULL }
};
#undef _L
int luaopen_delayExecution(lua_State* state){
luaL_newlib(state, delayExecutionLib_m);
//Create the metatable for the delay execution userdata.
luaL_newmetatable(state, "delayExecution");
lua_pushstring(state, "__index");
lua_pushvalue(state, -2);
lua_settable(state, -3);
ScriptDelayExecution::registerMetatableFunctions(state, -3);
//Register the functions and methods.
luaL_setfuncs(state, delayExecutionLib_m, 0);
return 1;
}
/////////////////////////GETTEXT SPECIFIC///////////////////////////
namespace gettext {
int gettext(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string); //msgid
if (levels) {
auto dm = levels->getDictionaryManager();
if (dm) {
lua_pushstring(state, dm->get_dictionary().translate(lua_tostring(state, 1)).c_str());
return 1;
}
}
//If we failed to find dictionay manager, we just return the original string.
lua_pushvalue(state, 1);
return 1;
}
int pgettext(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string); //msgctxt
HELPER_CHECK_ARGS_TYPE(2, string); //msgid
if (levels) {
auto dm = levels->getDictionaryManager();
if (dm) {
lua_pushstring(state, dm->get_dictionary().translate_ctxt(lua_tostring(state, 1), lua_tostring(state, 2)).c_str());
return 1;
}
}
//If we failed to find dictionay manager, we just return the original string.
lua_pushvalue(state, 2);
return 1;
}
int ngettext(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string); //msgid
HELPER_CHECK_ARGS_TYPE(2, string); //msgid_plural
HELPER_CHECK_ARGS_TYPE(3, number);
if (levels) {
auto dm = levels->getDictionaryManager();
if (dm) {
lua_pushstring(state, dm->get_dictionary().translate_plural(
lua_tostring(state, 1),
lua_tostring(state, 2),
lua_tonumber(state, 3)
).c_str());
return 1;
}
}
//If we failed to find dictionay manager, we just return the original string.
if (lua_tonumber(state, 3) == 1) {
lua_pushvalue(state, 1);
} else {
lua_pushvalue(state, 2);
}
return 1;
}
int npgettext(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(4);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string); //msgctxt
HELPER_CHECK_ARGS_TYPE(2, string); //msgid
HELPER_CHECK_ARGS_TYPE(3, string); //msgid_plural
HELPER_CHECK_ARGS_TYPE(4, number);
if (levels) {
auto dm = levels->getDictionaryManager();
if (dm) {
lua_pushstring(state, dm->get_dictionary().translate_ctxt_plural(
lua_tostring(state, 1),
lua_tostring(state, 2),
lua_tostring(state, 3),
lua_tonumber(state, 4)
).c_str());
return 1;
}
}
//If we failed to find dictionay manager, we just return the original string.
if (lua_tonumber(state, 4) == 1) {
lua_pushvalue(state, 2);
} else {
lua_pushvalue(state, 3);
}
return 1;
}
}
#define _L gettext
static const luaL_Reg gettextlib_m[] = {
_F(gettext),
_F(pgettext),
_F(ngettext),
_F(npgettext),
{ NULL, NULL }
};
#undef _L
int luaopen_gettext(lua_State* state){
//Register the global shortcut function _() and __().
luaL_loadstring(state,
"function _(s)\n"
" return gettext.gettext(s)\n"
"end\n"
"function __(s)\n"
" return s\n"
"end\n"
);
lua_pcall(state, 0, 0, 0);
luaL_newlib(state, gettextlib_m);
//Register the functions and methods.
luaL_setfuncs(state, gettextlib_m, 0);
return 1;
}
+
+//////////////////////////PRNG SPECIFIC///////////////////////////
+
+namespace prng {
+
+ int random(lua_State* state){
+ //Check the number of arguments.
+ HELPER_GET_AND_CHECK_ARGS_RANGE(0, 2);
+
+ //Check if the arguments are of the right type.
+ for (int i = 1; i <= args; i++) {
+ HELPER_CHECK_ARGS_TYPE(i, number);
+ }
+
+ //Check if the currentState is the game state.
+ Game* game = dynamic_cast<Game*>(currentState);
+ if (game == NULL) return 0;
+
+ if (args == 0) {
+ // float with uniform distribution in the range [0,1)
+ std::uniform_real_distribution<lua_Number> distribution;
+ lua_pushnumber(state, distribution(game->prng));
+ } else {
+ lua_Integer min = 1, max = 1;
+
+ if (args == 2) {
+ if (lua_isinteger(state, 1)) min = lua_tointeger(state, 1);
+ else min = (lua_Integer)lua_tonumber(state, 1);
+ }
+ if (lua_isinteger(state, args)) max = lua_tointeger(state, args);
+ else max = (lua_Integer)lua_tonumber(state, args);
+
+ unsigned long long size = max - min + 1;
+
+ if (size == 0) {
+ // this means 2^64
+ unsigned long long result = (((unsigned long long)game->prng()) << 32) | ((unsigned long long)game->prng());
+ lua_pushinteger(state, (lua_Integer)result);
+ } else if (size == 1) {
+ // no random at all
+ lua_pushinteger(state, min);
+ } else {
+ std::uniform_int_distribution<unsigned long long> distribution(0, size - 1);
+ unsigned long long result = distribution(game->prng);
+ lua_pushinteger(state, (lua_Integer)(min + result));
+ }
+ }
+
+ return 1;
+ }
+
+ int getSeed(lua_State* state){
+ //NOTE: this function accepts 0 arguments, but we ignore the argument count.
+
+ //Check if the currentState is the game state.
+ Game* game = dynamic_cast<Game*>(currentState);
+ if (game == NULL) return 0;
+
+ //Returns random seed.
+ lua_pushstring(state, game->prngSeed.c_str());
+ return 1;
+ }
+
+ int setSeed(lua_State* state){
+ //Check the number of arguments.
+ HELPER_GET_AND_CHECK_ARGS(1);
+
+ //Check if the arguments are of the right type.
+ HELPER_CHECK_ARGS_TYPE(1, string);
+
+ //Check if the currentState is the game state.
+ Game* game = dynamic_cast<Game*>(currentState);
+ if (game == NULL) return 0;
+
+ game->prngSeed = lua_tostring(state, 1);
+#ifdef _DEBUG
+ cout << "New PRNG seed by script: " << game->prngSeed << endl;
+#endif
+ game->prng.seed(std::seed_seq(game->prngSeed.begin(), game->prngSeed.end()));
+
+ return 0;
+ }
+
+}
+
+#define _L prng
+//Array with the methods for the level library.
+static const luaL_Reg prnglib_m[] = {
+ _F(random),
+ _FGS(Seed),
+ { NULL, NULL }
+};
+#undef _L
+
+int luaopen_prng(lua_State* state){
+ luaL_newlib(state, prnglib_m);
+
+ //Register the functions and methods.
+ luaL_setfuncs(state, prnglib_m, 0);
+ return 1;
+}
diff --git a/src/ScriptAPI.h b/src/ScriptAPI.h
index 94e8637..bf5a357 100644
--- a/src/ScriptAPI.h
+++ b/src/ScriptAPI.h
@@ -1,46 +1,48 @@
/*
* Copyright (C) 2012-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SCRIPTAPI_H
#define SCRIPTAPI_H
extern "C" {
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}
class ScriptExecutor;
//Method for loading the block library.
int luaopen_block(lua_State* state);
//Method for loading the player and shadow library.
int luaopen_player(lua_State* state);
//Method for loading the level library.
int luaopen_level(lua_State* state);
//Method for loading the camera library.
int luaopen_camera(lua_State* state);
//Method for loading the audio library.
int luaopen_audio(lua_State* state);
//Method for loading the delayExecution library.
int luaopen_delayExecution(lua_State* state);
//Method for loading the gettext library.
int luaopen_gettext(lua_State* state);
+//Method for loading the prng library.
+int luaopen_prng(lua_State* state);
#endif
diff --git a/src/ScriptExecutor.cpp b/src/ScriptExecutor.cpp
index 85ce3af..6837e0e 100644
--- a/src/ScriptExecutor.cpp
+++ b/src/ScriptExecutor.cpp
@@ -1,215 +1,216 @@
/*
* Copyright (C) 2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ScriptExecutor.h"
#include "ScriptDelayExecution.h"
#include "ScriptAPI.h"
#include "Block.h"
#include <iostream>
using namespace std;
ScriptExecutor::ScriptExecutor()
: state(NULL)
, delayExecutionObjects(NULL), savedDelayExecutionObjects(NULL)
{
//NOTE: If a state is going to use the scriptExecutor it is his task to reset it.
}
ScriptExecutor::~ScriptExecutor(){
destroy();
}
void ScriptExecutor::destroy() {
//Make sure there is a state to close.
if (state) {
lua_close(state);
state = NULL;
}
//Delete all delay execution objects.
if (delayExecutionObjects) {
// Set the state to NULL since the state is already destroyed. This prevents the destructor to call luaL_unref.
delayExecutionObjects->state = NULL;
delete delayExecutionObjects;
delayExecutionObjects = NULL;
}
if (savedDelayExecutionObjects) {
// Set the state to NULL since the state is already destroyed. This prevents the destructor to call luaL_unref.
savedDelayExecutionObjects->state = NULL;
delete savedDelayExecutionObjects;
savedDelayExecutionObjects = NULL;
}
}
void ScriptExecutor::reset(bool save){
//Check if we only need to restore from saved state.
if (!save && state) {
//Delete delay execution objects.
if (delayExecutionObjects) {
delete delayExecutionObjects;
delayExecutionObjects = new ScriptDelayExecutionList();
delayExecutionObjects->state = state;
}
return;
}
//Close the lua_state, if any.
destroy();
//Create a new state.
state=luaL_newstate();
//Now load the lua libraries.
//FIXME: Only allow safe libraries/functions.
luaopen_base(state);
luaL_requiref(state,"table",luaopen_table,1);
luaL_requiref(state,"coroutine",luaopen_coroutine,1);
luaL_requiref(state,"string",luaopen_string,1);
luaL_requiref(state,"utf8",luaopen_utf8,1);
luaL_requiref(state,"math",luaopen_math,1);
//Load our own libraries.
luaL_requiref(state,"block",luaopen_block,1);
luaL_requiref(state,"playershadow",luaopen_player,1);
luaL_requiref(state,"level",luaopen_level,1);
luaL_requiref(state,"camera",luaopen_camera,1);
luaL_requiref(state,"audio",luaopen_audio,1);
luaL_requiref(state,"delayExecution",luaopen_delayExecution,1);
luaL_requiref(state,"gettext",luaopen_gettext,1);
+ luaL_requiref(state,"prng",luaopen_prng,1);
//Create a new delay execution list.
delayExecutionObjects = new ScriptDelayExecutionList();
delayExecutionObjects->state = state;
}
void ScriptExecutor::registerFunction(std::string name,lua_CFunction function){
lua_register(state,name.c_str(),function);
}
int ScriptExecutor::compileScript(std::string script){
//First make sure the stack is empty.
lua_settop(state,0);
//Compile the script.
if(luaL_loadstring(state,script.c_str())!=LUA_OK){
cerr<<"LUA ERROR: "<<lua_tostring(state,-1)<<endl;
lua_pushnil(state);
}
//Save it to LUA_REGISTRYINDEX and return values.
return luaL_ref(state,LUA_REGISTRYINDEX);
}
int ScriptExecutor::executeScript(std::string script,Block* origin){
//First make sure the stack is empty.
lua_settop(state,0);
//Compile the script.
if(luaL_loadstring(state,script.c_str())!=LUA_OK){
cerr<<"LUA ERROR: "<<lua_tostring(state,-1)<<endl;
return 0;
}
//Now execute the script.
return executeScriptInternal(origin);
}
int ScriptExecutor::executeScript(int scriptIndex,Block* origin){
//Check if reference is empty.
if(scriptIndex==LUA_REFNIL) return 0;
//Make sure the stack is empty.
lua_settop(state,0);
//Get the function
lua_rawgeti(state,LUA_REGISTRYINDEX,scriptIndex);
//Check if it's function and run.
if(lua_isfunction(state,-1)){
return executeScriptInternal(origin);
}
return 0;
}
int ScriptExecutor::executeScriptInternal(Block* origin){
int oldThisIndex = LUA_REFNIL;
//If the origin isn't null set it in the global scope.
if(origin){
//Backup the old "this"
lua_getglobal(state, "this");
oldThisIndex = luaL_ref(state, LUA_REGISTRYINDEX);
origin->createUserData(state,"block");
lua_setglobal(state,"this");
}
//Now execute the script on the top of Lua stack.
int ret=lua_pcall(state,0,1,0);
//If we set an origin set it back to oringinal value.
if(origin){
lua_rawgeti(state, LUA_REGISTRYINDEX, oldThisIndex);
luaL_unref(state, LUA_REGISTRYINDEX, oldThisIndex);
lua_setglobal(state,"this");
}
//Check if there's an error.
if(ret!=LUA_OK){
cerr<<"LUA ERROR: "<<lua_tostring(state,-1)<<endl;
return 0;
}
//Get the return value.
return lua_tointeger(state,-1);
}
ScriptDelayExecutionList* ScriptExecutor::getDelayExecutionList() {
return delayExecutionObjects;
}
void ScriptExecutor::processDelayExecution() {
if (delayExecutionObjects) delayExecutionObjects->updateTimer();
}
void ScriptExecutor::saveState() {
if (savedDelayExecutionObjects) {
delete savedDelayExecutionObjects;
savedDelayExecutionObjects = NULL;
}
savedDelayExecutionObjects = new ScriptDelayExecutionList(*delayExecutionObjects);
}
void ScriptExecutor::loadState() {
if (delayExecutionObjects) {
delete delayExecutionObjects;
delayExecutionObjects = NULL;
}
if (savedDelayExecutionObjects) {
delayExecutionObjects = new ScriptDelayExecutionList(*savedDelayExecutionObjects);
for (auto obj : delayExecutionObjects->objects) {
obj->setActive();
}
} else {
delayExecutionObjects = new ScriptDelayExecutionList();
delayExecutionObjects->state = state;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, May 16, 7:14 PM (1 d, 4 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
63217
Default Alt Text
(196 KB)

Event Timeline