/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program 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.
*
* This program 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 this program. If not, see .
*
*/
#include "twp/twp.h"
#include "twp/detection.h"
#include "twp/object.h"
#include "twp/room.h"
#include "twp/shaders.h"
#include "twp/sqgame.h"
#include "twp/squtil.h"
namespace Twp {
static SQInteger addTrigger(HSQUIRRELVM v) {
SQInteger nArgs = sq_gettop(v);
Common::SharedPtr obj = sqobj(v, 2);
if (!obj)
return sq_throwerror(v, "failed to get object");
sq_resetobject(&obj->_enter);
sq_resetobject(&obj->_leave);
if (SQ_FAILED(sqget(v, 3, obj->_enter)))
return sq_throwerror(v, "failed to get enter");
sq_addref(g_twp->getVm(), &obj->_enter);
if (nArgs == 4)
if (SQ_FAILED(sqget(v, 4, obj->_leave)))
return sq_throwerror(v, "failed to get leave");
sq_addref(g_twp->getVm(), &obj->_leave);
obj->_triggerActive = false;
g_twp->_room->_triggers.push_back(obj);
return 0;
}
static SQInteger clampInWalkbox(HSQUIRRELVM v) {
SQInteger numArgs = sq_gettop(v);
Math::Vector2d pos1, pos2;
if (numArgs == 3) {
SQInteger x = 0;
if (SQ_FAILED(sqget(v, 2, x)))
return sq_throwerror(v, "failed to get x");
SQInteger y = 0;
if (SQ_FAILED(sqget(v, 3, y)))
return sq_throwerror(v, "failed to get y");
pos1 = Math::Vector2d(x, y);
pos2 = pos1;
} else if (numArgs == 5) {
SQInteger x1 = 0;
if (SQ_FAILED(sqget(v, 2, x1)))
return sq_throwerror(v, "failed to get x1");
SQInteger y1 = 0;
if (SQ_FAILED(sqget(v, 3, y1)))
return sq_throwerror(v, "failed to get y1");
pos1 = Math::Vector2d(x1, y1);
SQInteger x2 = 0;
if (SQ_FAILED(sqget(v, 4, x2)))
return sq_throwerror(v, "failed to get x2");
SQInteger y2 = 0;
if (SQ_FAILED(sqget(v, 5, y1)))
return sq_throwerror(v, "failed to get y2");
pos2 = Math::Vector2d(x2, y2);
} else {
return sq_throwerror(v, "Invalid argument number in clampInWalkbox");
}
const Common::Array &walkboxes = g_twp->_room->_walkboxes;
for (size_t i = 0; i < walkboxes.size(); i++) {
const Walkbox &walkbox = walkboxes[i];
if (walkbox.contains(pos1)) {
sqpush(v, pos1);
return 1;
}
}
Math::Vector2d pos = walkboxes[0].getClosestPointOnEdge(pos2);
sqpush(v, Common::move(pos));
return 1;
}
static SQInteger createLight(HSQUIRRELVM v) {
SQInteger color;
if (SQ_FAILED(sqget(v, 2, color)))
return sq_throwerror(v, "failed to get color");
SQInteger x;
if (SQ_FAILED(sqget(v, 3, x)))
return sq_throwerror(v, "failed to get x");
SQInteger y;
if (SQ_FAILED(sqget(v, 4, y)))
return sq_throwerror(v, "failed to get y");
Light *light = g_twp->_room->createLight(Color::rgb(color), Math::Vector2d(x, y));
debugC(kDebugRoomScript, "createLight(%lld) -> %d", color, light->id);
sqpush(v, light->id);
return 1;
}
static SQInteger enableTrigger(HSQUIRRELVM v) {
Common::SharedPtr obj = sqobj(v, 2);
if (!obj)
return sq_throwerror(v, "failed to get object");
bool enabled;
if (SQ_FAILED(sqget(v, 3, enabled)))
return sq_throwerror(v, "failed to get enabled");
if (enabled) {
g_twp->_room->_triggers.push_back(obj);
} else {
int index = find(g_twp->_room->_triggers, obj);
if (index != -1)
g_twp->_room->_triggers.remove_at(index);
}
return 0;
}
static SQInteger enterRoomFromDoor(HSQUIRRELVM v) {
Common::SharedPtr obj = sqobj(v, 2);
if (!obj)
return sq_throwerror(v, "failed to get object");
g_twp->enterRoom(obj->_room, obj);
return 0;
}
static SQInteger lightBrightness(HSQUIRRELVM v) {
Light *light = sqlight(v, 2);
if (light) {
float brightness;
if (SQ_FAILED(sqget(v, 3, brightness)))
return sq_throwerror(v, "failed to get brightness");
light->brightness = brightness;
}
return 0;
}
static SQInteger lightConeDirection(HSQUIRRELVM v) {
Light *light = sqlight(v, 2);
if (light) {
float direction;
if (SQ_FAILED(sqget(v, 3, direction)))
return sq_throwerror(v, "failed to get direction");
light->coneDirection = direction;
}
return 0;
}
static SQInteger lightConeAngle(HSQUIRRELVM v) {
Light *light = sqlight(v, 2);
if (light) {
float angle;
if (SQ_FAILED(sqget(v, 3, angle)))
return sq_throwerror(v, "failed to get angle");
light->coneAngle = angle;
}
return 0;
}
static SQInteger lightConeFalloff(HSQUIRRELVM v) {
Light *light = sqlight(v, 2);
if (light) {
float falloff;
if (SQ_FAILED(sqget(v, 3, falloff)))
return sq_throwerror(v, "failed to get falloff");
light->coneFalloff = falloff;
}
return 0;
}
static SQInteger lightCutOffRadius(HSQUIRRELVM v) {
Light *light = sqlight(v, 2);
if (light) {
float cutOffRadius;
if (SQ_FAILED(sqget(v, 3, cutOffRadius)))
return sq_throwerror(v, "failed to get cutOffRadius");
light->cutOffRadius = cutOffRadius;
}
return 0;
}
static SQInteger lightHalfRadius(HSQUIRRELVM v) {
Light *light = sqlight(v, 2);
if (light) {
float halfRadius;
if (SQ_FAILED(sqget(v, 3, halfRadius)))
return sq_throwerror(v, "failed to get halfRadius");
light->halfRadius = halfRadius;
}
return 0;
}
static SQInteger lightTurnOn(HSQUIRRELVM v) {
Light *light = sqlight(v, 2);
if (light) {
bool on;
if (SQ_FAILED(sqget(v, 3, on)))
return sq_throwerror(v, "failed to get on");
light->on = on;
}
return 0;
}
static SQInteger lightZRange(HSQUIRRELVM v) {
const Light *light = sqlight(v, 2);
if (light) {
SQInteger nearY, farY;
if (SQ_FAILED(sqget(v, 3, nearY)))
return sq_throwerror(v, "failed to get nearY");
if (SQ_FAILED(sqget(v, 4, farY)))
return sq_throwerror(v, "failed to get farY");
warning("lightZRange not implemented");
}
return 0;
}
static SQInteger defineRoom(HSQUIRRELVM v) {
// This command is used during the game's boot process.
// `defineRoom` is called once for every room in the game, passing it the room's room object.
// If the room has not been defined, it can not be referenced.
// `defineRoom` is typically called in the the DefineRooms.nut file which loads and defines every room in the game.
HSQOBJECT table;
sq_resetobject(&table);
if (SQ_FAILED(sq_getstackobj(v, 2, &table)))
return sq_throwerror(v, "failed to get room table");
Common::String name;
if (SQ_FAILED(sqgetf(v, table, "background", name))) {
return sq_throwerror(v, "failed to get room name");
}
Common::SharedPtr room = g_twp->defineRoom(name, table);
debugC(kDebugRoomScript, "Define room: %s", name.c_str());
g_twp->_rooms.push_back(room);
sqpush(v, room->_table);
return 1;
}
// Creates a new room called name using the specified template.
//
// . code-block:: Squirrel
// for (local room_id = 1; room_id <= HOTEL_ROOMS_PER_FLOOR; room_id++) {
// local room = definePseudoRoom("HotelRoomA"+((floor_id*100)+room_id), HotelRoomA)
// local door = floor["hotelHallDoor"+room_id]
// ...
// }
static SQInteger definePseudoRoom(HSQUIRRELVM v) {
const SQChar *name;
if (SQ_FAILED(sqget(v, 2, name)))
return sq_throwerror(v, "failed to get name");
HSQOBJECT table;
sq_resetobject(&table);
// if this is a pseudo room, we have to clone the table
// to have a different instance by room
if (SQ_FAILED(sq_clone(v, 3)))
return sq_throwerror(v, "failed to clone room table");
if (SQ_FAILED(sq_getstackobj(v, -1, &table)))
return sq_throwerror(v, "failed to get room table");
Common::SharedPtr room(g_twp->defineRoom(name, table, true));
debugC(kDebugRoomScript, "Define pseudo room: %s", name);
g_twp->_rooms.push_back(room);
sqpush(v, room->_table);
return 1;
}
// Returns the room table for the room specified by the string roomName.
// Useful for returning specific pseudo rooms where the name is composed of text and a variable.
//
// .. code-block:: Squirrel
// local standardRoom = findRoom("HotelRoomA"+keycard.room_num)
static SQInteger findRoom(HSQUIRRELVM v) {
Common::String name;
if (SQ_FAILED(sqget(v, 2, name)))
return sq_throwerror(v, "failed to get name");
for (size_t i = 0; i < g_twp->_rooms.size(); i++) {
Common::SharedPtr room = g_twp->_rooms[i];
if (room->_name == name) {
sqpush(v, room->_table);
return 1;
}
}
warning("Room '%s' not found", name.c_str());
sq_pushnull(v);
return 1;
}
// Returns an array of all the rooms that are in the game currently.
//
// This is useful for testing.
//
// .. code-block:: Squirrel
// local roomArray = masterRoomArray()
// foreach (room in roomArray) {
// enterRoomFromDoor(room)
// breaktime(0.10)
// }
static SQInteger masterRoomArray(HSQUIRRELVM v) {
sq_newarray(v, 0);
for (size_t i = 0; i < g_twp->_rooms.size(); i++) {
Common::SharedPtr room = g_twp->_rooms[i];
sq_pushobject(v, room->_table);
sq_arrayappend(v, -2);
}
return 1;
}
static SQInteger removeTrigger(HSQUIRRELVM v) {
if (!g_twp->_room)
return 0;
if (sq_gettype(v, 2) == OT_CLOSURE) {
HSQOBJECT closure;
sq_resetobject(&closure);
if (SQ_FAILED(sqget(v, 3, closure)))
return sq_throwerror(v, "failed to get closure");
for (size_t i = 0; i < g_twp->_room->_triggers.size(); i++) {
Common::SharedPtr trigger = g_twp->_room->_triggers[i];
if ((trigger->_enter._unVal.pClosure == closure._unVal.pClosure) || (trigger->_leave._unVal.pClosure == closure._unVal.pClosure)) {
g_twp->_room->_triggers.remove_at(i);
return 0;
}
}
} else {
Common::SharedPtr obj = sqobj(v, 2);
if (!obj)
return sq_throwerror(v, "failed to get object");
size_t i = find(g_twp->_room->_triggers, obj);
if (i != (size_t)-1) {
debugC(kDebugRoomScript, "Remove room trigger: %s(%s)", obj->_name.c_str(), obj->_key.c_str());
g_twp->_room->_triggers.remove_at(find(g_twp->_room->_triggers, obj));
}
return 0;
}
return 0;
}
// Returns an array of all the actors in the specified room.
//
// .. code-block:: Squirrel
// local actorInBookstore = roomActors(BookStore)
// if (actorInBookstore.len()>1) { ... }
//
// local spotters = roomActors(currentRoom)
// foreach(actor in spotters) { ...}
static SQInteger roomActors(HSQUIRRELVM v) {
Common::SharedPtr room(sqroom(v, 2));
if (!room)
return sq_throwerror(v, "failed to get room");
sq_newarray(v, 0);
for (size_t i = 0; i < g_twp->_actors.size(); i++) {
Common::SharedPtr actor = g_twp->_actors[i];
if (actor->_room == room) {
sqpush(v, actor->_table);
sq_arrayappend(v, -2);
}
}
return 1;
}
static SQInteger roomEffect(HSQUIRRELVM v) {
SQInteger effect = 0;
if (SQ_FAILED(sqget(v, 2, effect)))
return sq_throwerror(v, "failed to get effect");
RoomEffect roomEffect = (RoomEffect)effect;
SQInteger nArgs = sq_gettop(v);
if (roomEffect == RoomEffect::Ghost) {
if (nArgs == 14) {
if (SQ_FAILED(sqget(v, 3, g_twp->_shaderParams->iFade)))
return sq_throwerror(v, "failed to get iFade");
if (SQ_FAILED(sqget(v, 4, g_twp->_shaderParams->wobbleIntensity)))
return sq_throwerror(v, "failed to get wobbleIntensity");
if (SQ_FAILED(sqget(v, 6, g_twp->_shaderParams->shadows.rgba.r)))
return sq_throwerror(v, "failed to get shadows r");
if (SQ_FAILED(sqget(v, 7, g_twp->_shaderParams->shadows.rgba.g)))
return sq_throwerror(v, "failed to get shadows g");
if (SQ_FAILED(sqget(v, 8, g_twp->_shaderParams->shadows.rgba.b)))
return sq_throwerror(v, "failed to get shadows b");
if (SQ_FAILED(sqget(v, 9, g_twp->_shaderParams->midtones.rgba.r)))
return sq_throwerror(v, "failed to get midtones r");
if (SQ_FAILED(sqget(v, 10, g_twp->_shaderParams->midtones.rgba.g)))
return sq_throwerror(v, "failed to get midtones g");
if (SQ_FAILED(sqget(v, 11, g_twp->_shaderParams->midtones.rgba.b)))
return sq_throwerror(v, "failed to get midtones b");
if (SQ_FAILED(sqget(v, 12, g_twp->_shaderParams->highlights.rgba.r)))
return sq_throwerror(v, "failed to get highlights r");
if (SQ_FAILED(sqget(v, 13, g_twp->_shaderParams->highlights.rgba.g)))
return sq_throwerror(v, "failed to get highlights g");
if (SQ_FAILED(sqget(v, 14, g_twp->_shaderParams->highlights.rgba.b)))
return sq_throwerror(v, "failed to get highlights b");
} else {
g_twp->_shaderParams->iFade = 1.f;
g_twp->_shaderParams->wobbleIntensity = 1.f;
g_twp->_shaderParams->shadows = Color(-0.3f, 0.f, 0.f);
g_twp->_shaderParams->midtones = Color(-0.2f, 0.f, 0.1f);
g_twp->_shaderParams->highlights = Color(0.f, 0.f, 0.2f);
}
}
g_twp->_room->_effect = (RoomEffect)effect;
return 0;
}
// Fades in or out (FADE_IN, FADE_OUT, FADE_WOBBLE, FADE_WOBBLE_TO_SEPIA) of the current room over the specified duration.
//
// Used for dramatic effect when we want to teleport the player actor to somewhere new, or when starting/ending a cutscene that takes place in another room.
//
// .. code-block:: Squirrel
// roomFade(FADE_OUT, 0.5)
// breaktime(0.5)
// actorAt(currentActor, Alleyway.newLocationSpot)
// cameraFollow(currentActor)
// roomFade(FADE_IN, 0.5)
static SQInteger roomFade(HSQUIRRELVM v) {
SQInteger fadeType;
float t;
if (SQ_FAILED(sqget(v, 2, fadeType)))
return sq_throwerror(v, "failed to get fadeType");
if (SQ_FAILED(sqget(v, 3, t)))
return sq_throwerror(v, "failed to get time");
FadeEffect effect = FadeEffect::In;
bool sepia = false;
switch (fadeType) {
case FADE_IN:
break;
case FADE_OUT:
effect = FadeEffect::Out;
break;
case FADE_WOBBLE:
effect = FadeEffect::Wobble;
break;
case FADE_WOBBLE_TO_SEPIA:
effect = FadeEffect::Wobble;
sepia = true;
break;
default:
break;
}
g_twp->fadeTo(effect, t, sepia);
return 0;
}
// Makes all layers at the specified zsort value in room visible (YES) or invisible (NO).
// It's also currently the only way to affect parallax layers and can be used for minor animation to turn a layer on and off.
//
// .. code-block:: Squirrel
// roomLayer(GrateEntry, -2, NO) // Make lights out layer invisible
static SQInteger roomLayer(HSQUIRRELVM v) {
Common::SharedPtr r = sqroom(v, 2);
SQInteger layer;
SQInteger enabled;
if (SQ_FAILED(sqget(v, 3, layer)))
return sq_throwerror(v, "failed to get layer");
if (SQ_FAILED(sq_getinteger(v, 4, &enabled)))
return sq_throwerror(v, "failed to get enabled");
r->layer(layer)->_node->setVisible(enabled != 0);
return 0;
}
// Puts a color overlay on the top of the entire room.
//
// Transition from startColor to endColor over duration seconds.
// The endColor remains on screen until changed.
// Note that the actual colour is an 8 digit number, the first two digits (00-ff) represent the transparency, while the last 6 digits represent the actual colour.
// If transparency is set to 00, the overlay is completely see through.
// If startColor is not on the screen already, it will flash to that color before starting the transition.
// If no endColor or duration are provided, it will change instantly to color and remain there.
//
// .. code-block:: Squirrel
// // Make lights in QuickiePal flicker
// roomOverlayColor(0x20dff2cd, 0x20dff2cd, 0.0)
// breaktime(1/60)
// roomOverlayColor(0x00000000, 0x00000000, 0.0)
// breaktime(1/60)
//
// if (currentActor == franklin) {
// roomOverlayColor(0x800040AA)
// }
static SQInteger roomOverlayColor(HSQUIRRELVM v) {
SQInteger startColor;
SQInteger numArgs = sq_gettop(v);
if (SQ_FAILED(sqget(v, 2, startColor)))
return sq_throwerror(v, "failed to get startColor");
Common::SharedPtr room = g_twp->_room;
if (room->_overlayTo)
room->_overlayTo->disable();
room->setOverlay(Color::fromRgba(startColor));
if (numArgs == 4) {
SQInteger endColor;
if (SQ_FAILED(sqget(v, 3, endColor)))
return sq_throwerror(v, "failed to get endColor");
float duration;
if (SQ_FAILED(sqget(v, 4, duration)))
return sq_throwerror(v, "failed to get duration");
debugC(kDebugRoomScript, "start overlay from {rgba(startColor)} to {rgba(endColor)} in {duration}s");
g_twp->_room->_overlayTo = Common::SharedPtr(new OverlayTo(duration, room, Color::fromRgba(endColor)));
}
return 0;
}
static SQInteger roomRotateTo(HSQUIRRELVM v) {
float rotation;
if (SQ_FAILED(sqget(v, 2, rotation)))
return sq_throwerror(v, "failed to get rotation");
g_twp->_room->_rotateTo = Common::SharedPtr(new RoomRotateTo(g_twp->_room, rotation));
return 0;
}
static SQInteger roomSize(HSQUIRRELVM v) {
Common::SharedPtr room = sqroom(v, 2);
if (!room)
return sq_throwerror(v, "failed to get room");
sqpush(v, room->_roomSize);
return 1;
}
static SQInteger setAmbientLight(HSQUIRRELVM v) {
SQInteger c = 0;
if (SQ_FAILED(sqget(v, 2, c)))
return sq_throwerror(v, "failed to get color");
g_twp->_room->_lights._ambientLight = Color::rgb(c);
return 0;
}
// Sets walkbox to be hidden (YES) or not (NO).
// If the walkbox is hidden, the actors cannot walk to any point within that area anymore, nor to any walkbox that's connected to it on the other side from the actor.
// Often used on small walkboxes below a gate or door to keep the actor from crossing that boundary if the gate/door is closed.
static SQInteger walkboxHidden(HSQUIRRELVM v) {
Common::String walkbox;
if (SQ_FAILED(sqget(v, 2, walkbox)))
return sq_throwerror(v, "failed to get object or walkbox");
SQInteger hidden = 0;
if (SQ_FAILED(sqget(v, 3, hidden)))
return sq_throwerror(v, "failed to get object or hidden");
g_twp->_room->walkboxHidden(walkbox, hidden != 0);
return 0;
}
void sqgame_register_roomlib(HSQUIRRELVM v) {
regFunc(v, addTrigger, "addTrigger");
regFunc(v, clampInWalkbox, "clampInWalkbox");
regFunc(v, createLight, "createLight");
regFunc(v, defineRoom, "defineRoom");
regFunc(v, definePseudoRoom, "definePseudoRoom");
regFunc(v, enableTrigger, "enableTrigger");
regFunc(v, enterRoomFromDoor, "enterRoomFromDoor");
regFunc(v, findRoom, "findRoom");
regFunc(v, lightBrightness, "lightBrightness");
regFunc(v, lightConeAngle, "lightConeAngle");
regFunc(v, lightConeDirection, "lightConeDirection");
regFunc(v, lightConeFalloff, "lightConeFalloff");
regFunc(v, lightCutOffRadius, "lightCutOffRadius");
regFunc(v, lightHalfRadius, "lightHalfRadius");
regFunc(v, lightTurnOn, "lightTurnOn");
regFunc(v, lightZRange, "lightZRange");
regFunc(v, masterRoomArray, "masterRoomArray");
regFunc(v, removeTrigger, "removeTrigger");
regFunc(v, roomActors, "roomActors");
regFunc(v, roomEffect, "roomEffect");
regFunc(v, roomFade, "roomFade");
regFunc(v, roomLayer, "roomLayer");
regFunc(v, roomRotateTo, "roomRotateTo");
regFunc(v, roomSize, "roomSize");
regFunc(v, roomOverlayColor, "roomOverlayColor");
regFunc(v, setAmbientLight, _SC("setAmbientLight"));
regFunc(v, walkboxHidden, "walkboxHidden");
}
} // namespace Twp