3006 lines
91 KiB
C++
3006 lines
91 KiB
C++
/* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "ultima/nuvie/core/nuvie_defs.h"
|
|
#include "ultima/nuvie/misc/u6_llist.h"
|
|
#include "ultima/nuvie/misc/u6_misc.h"
|
|
#include "ultima/nuvie/misc/map_entity.h"
|
|
#include "ultima/nuvie/core/game.h"
|
|
#include "ultima/nuvie/views/view.h"
|
|
#include "ultima/nuvie/views/view_manager.h"
|
|
#include "ultima/nuvie/actors/actor_manager.h"
|
|
#include "ultima/nuvie/actors/actor.h"
|
|
#include "ultima/nuvie/actors/u6_actor.h"
|
|
#include "ultima/nuvie/core/party.h"
|
|
#include "ultima/nuvie/core/player.h"
|
|
#include "ultima/nuvie/gui/widgets/msg_scroll.h"
|
|
#include "ultima/nuvie/core/map.h"
|
|
#include "ultima/nuvie/core/game_clock.h"
|
|
#include "ultima/nuvie/core/book.h"
|
|
#include "ultima/nuvie/core/events.h"
|
|
#include "ultima/nuvie/gui/widgets/map_window.h"
|
|
#include "ultima/nuvie/core/timed_event.h"
|
|
#include "ultima/nuvie/core/egg_manager.h"
|
|
#include "ultima/nuvie/core/anim_manager.h"
|
|
#include "ultima/nuvie/sound/sound_manager.h"
|
|
#include "ultima/nuvie/core/effect.h"
|
|
#include "ultima/nuvie/core/weather.h"
|
|
#include "ultima/nuvie/script/script.h"
|
|
#include "ultima/nuvie/keybinding/keys.h"
|
|
#include "ultima/nuvie/gui/widgets/background.h"
|
|
#include "ultima/nuvie/gui/widgets/command_bar.h"
|
|
#include "ultima/nuvie/usecode/u6_usecode.h"
|
|
#include "ultima/nuvie/usecode/u6_object_types.h"
|
|
#include "ultima/nuvie/actors/u6_work_types.h"
|
|
|
|
namespace Ultima {
|
|
namespace Nuvie {
|
|
|
|
#define MESG_ANIM_HIT_WORLD ANIM_CB_HIT_WORLD
|
|
#define MESG_ANIM_HIT ANIM_CB_HIT
|
|
#define MESG_TEXT_READY MSGSCROLL_CB_TEXT_READY
|
|
#define MESG_DATA_READY CB_DATA_READY
|
|
#define MESG_EFFECT_COMPLETE EFFECT_CB_COMPLETE
|
|
#define MESG_TIMED CB_TIMED
|
|
#define MESG_INPUT_CANCELED CB_INPUT_CANCELED
|
|
|
|
// numbered by entrance quality, "" = no name
|
|
static const char *u6_dungeons[21] = {
|
|
"",
|
|
"Deceit",
|
|
"Despise",
|
|
"Destard",
|
|
"Wrong",
|
|
"Covetous",
|
|
"Shame",
|
|
"Hythloth",
|
|
"GSA",
|
|
"Control",
|
|
"Passion",
|
|
"Diligence",
|
|
"Tomb of Kings",
|
|
"Ant Mound",
|
|
"Swamp Cave",
|
|
"Spider Cave",
|
|
"Cyclops Cave",
|
|
"Heftimus Cave",
|
|
"Heroes' Hole",
|
|
"Pirate Cave",
|
|
"Buccaneer's Cave"
|
|
};
|
|
|
|
// Red moongate teleport locations.
|
|
static const struct {
|
|
uint16 x;
|
|
uint16 y;
|
|
uint8 z;
|
|
} red_moongate_tbl[] = {
|
|
{0x0, 0x0, 0x0},
|
|
{0x383, 0x1f3, 0x0},
|
|
{0x3a7, 0x106, 0x0},
|
|
{0x1b3, 0x18b, 0x0},
|
|
{0x1f7, 0x166, 0x0},
|
|
{0x93, 0x373, 0x0},
|
|
{0x397, 0x3a6, 0x0},
|
|
{0x44, 0x2d, 0x5},
|
|
{0x133, 0x160, 0x0},
|
|
{0xbc, 0x2d, 0x5},
|
|
{0x9f, 0x3ae, 0x0},
|
|
{0x2e3, 0x2bb, 0x0},
|
|
{0x0, 0x0, 0x0},
|
|
{0x0, 0x0, 0x0},
|
|
{0x0, 0x0, 0x0},
|
|
{0xe3, 0x83, 0x0},
|
|
{0x17, 0x16, 0x1},
|
|
{0x80, 0x56, 0x5},
|
|
{0x6c, 0xdd, 0x5},
|
|
{0x39b, 0x36c, 0x0},
|
|
{0x127, 0x26, 0x0},
|
|
{0x4b, 0x1fb, 0x0},
|
|
{0x147, 0x336, 0x0},
|
|
{0x183, 0x313, 0x0},
|
|
{0x33f, 0xa6, 0x0},
|
|
{0x29b, 0x43, 0x0}
|
|
};
|
|
|
|
static const uint8 USE_U6_POTION_BLUE = 0;
|
|
static const uint8 USE_U6_POTION_RED = 1;
|
|
static const uint8 USE_U6_POTION_YELLOW = 2;
|
|
static const uint8 USE_U6_POTION_GREEN = 3;
|
|
static const uint8 USE_U6_POTION_ORANGE = 4;
|
|
static const uint8 USE_U6_POTION_PURPLE = 5;
|
|
static const uint8 USE_U6_POTION_BLACK = 6;
|
|
static const uint8 USE_U6_POTION_WHITE = 7;
|
|
|
|
// numbered by potion object frame number
|
|
static const char *u6_potions[8] = {
|
|
"an awaken", // blue
|
|
"a cure", // red
|
|
"a heal", // yellow
|
|
"a poison", // green
|
|
"a sleep", // orange
|
|
"a protection", // purple
|
|
"an invisibility", // black
|
|
"an xray vision" // white
|
|
};
|
|
|
|
// convenient macro to grab a target object - when first called the player will
|
|
// be prompted with P, and upon target selection the object will be set to O
|
|
#define USECODE_SELECT_OBJ(O, P) \
|
|
{ \
|
|
static bool selected_obj = false; \
|
|
if(!selected_obj) \
|
|
{ \
|
|
game->get_event()->get_direction(P); \
|
|
game->get_event()->request_input(this, obj); \
|
|
selected_obj = true; \
|
|
return false; \
|
|
} \
|
|
else { O = items.obj_ref; selected_obj = false; } \
|
|
}
|
|
#define USECODE_SELECT_ACTOR(O, P) \
|
|
{ \
|
|
static bool selected_obj = false; \
|
|
if(!selected_obj) \
|
|
{ \
|
|
game->get_event()->get_target(P); \
|
|
game->get_event()->request_input(this, obj); \
|
|
selected_obj = true; \
|
|
return false; \
|
|
} \
|
|
else { A = items.actor2_ref; selected_obj = false; } \
|
|
}
|
|
#define USECODE_SELECT_TARGET(T, P) \
|
|
{ \
|
|
static bool selected_obj = false; \
|
|
if(!selected_obj) \
|
|
{ \
|
|
game->get_event()->get_target(P); \
|
|
game->get_event()->request_input(this, obj); \
|
|
selected_obj = true; \
|
|
return false; \
|
|
} \
|
|
else { T = items.mapcoord_ref; selected_obj = false; } \
|
|
}
|
|
#define USECODE_SELECT_DIRECTION(T, P) \
|
|
{ \
|
|
static bool selected_obj = false; \
|
|
if(!selected_obj) \
|
|
{ \
|
|
game->get_event()->get_target(P); \
|
|
game->get_event()->request_input(this, obj); \
|
|
selected_obj = true; \
|
|
return false; \
|
|
} \
|
|
else { T = items.mapcoord_ref; selected_obj = false; } \
|
|
}
|
|
|
|
|
|
U6UseCode::U6UseCode(Game *g, const Configuration *cfg) : UseCode(g, cfg) {
|
|
}
|
|
|
|
U6UseCode::~U6UseCode() {
|
|
}
|
|
|
|
|
|
/* Is the object a food (or drink) item? */
|
|
bool U6UseCode::is_food(const Obj *obj) const {
|
|
const U6ObjectType *type = get_object_type(obj->obj_n, obj->frame_n);
|
|
return (type && (type->flags & OBJTYPE_FOOD));
|
|
}
|
|
|
|
|
|
bool U6UseCode::is_container(const Obj *obj) const {
|
|
const U6ObjectType *type = get_object_type(obj->obj_n, obj->frame_n);
|
|
return (type && (type->flags & OBJTYPE_CONTAINER));
|
|
}
|
|
|
|
bool U6UseCode::is_container(uint16 obj_n, uint8 frame_n) const {
|
|
const U6ObjectType *type = get_object_type(obj_n, frame_n);
|
|
return (type && (type->flags & OBJTYPE_CONTAINER));
|
|
}
|
|
|
|
bool U6UseCode::is_readable(const Obj *obj) const {
|
|
const U6ObjectType *type = get_object_type(obj->obj_n, obj->frame_n);
|
|
return ((type && (type->flags & OBJTYPE_BOOK)) || obj->obj_n == OBJ_U6_CLOCK
|
|
|| obj->obj_n == OBJ_U6_SUNDIAL);
|
|
}
|
|
|
|
|
|
/* Is there `ev' usecode for an object? */
|
|
bool U6UseCode::has_usecode(Obj *obj, UseCodeEvent ev) {
|
|
const U6ObjectType *type = get_object_type(obj->obj_n, obj->frame_n, ev);
|
|
if (!type && !UseCode::has_usecode(obj, ev))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool U6UseCode::has_usecode(Actor *actor, UseCodeEvent ev) {
|
|
const U6ObjectType *type = get_object_type(actor->get_obj_n(), actor->get_frame_n(), ev);
|
|
if (!type || type->flags == OBJTYPE_CONTAINER)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
/* USE object. Actor is the actor using the object. */
|
|
bool U6UseCode::use_obj(Obj *obj, Actor *actor) {
|
|
if (UseCode::has_usecode(obj, USE_EVENT_USE)) { //use script based usecode.
|
|
return UseCode::use_obj(obj, actor);
|
|
}
|
|
|
|
const U6ObjectType *type = get_object_type(obj->obj_n, obj->frame_n, USE_EVENT_USE);
|
|
set_itemref(actor, items.actor2_ref);
|
|
return (uc_event(type, USE_EVENT_USE, obj));
|
|
}
|
|
|
|
/* LOOK at object. Actor is the actor looking at the object. */
|
|
bool U6UseCode::look_obj(Obj *obj, Actor *actor) {
|
|
const U6ObjectType *type = get_object_type(obj->obj_n, obj->frame_n, USE_EVENT_LOOK);
|
|
set_itemref(actor);
|
|
return (uc_event(type, USE_EVENT_LOOK, obj));
|
|
}
|
|
|
|
/* PASS object. Actor is the actor trying to pass the object. It takes
|
|
target coordinates in case the object has multiple tiles. */
|
|
bool U6UseCode::pass_obj(Obj *obj, Actor *actor, uint16 x, uint16 y) {
|
|
const U6ObjectType *type = get_object_type(obj->obj_n, obj->frame_n, USE_EVENT_PASS);
|
|
static MapCoord loc;
|
|
loc.x = x;
|
|
loc.y = y;
|
|
loc.z = obj->z;
|
|
set_itemref(actor);
|
|
set_itemref(&loc);
|
|
return (uc_event(type, USE_EVENT_PASS, obj));
|
|
}
|
|
|
|
/* SEARCH nearby object. Actor is the actor searching. */
|
|
bool U6UseCode::search_obj(Obj *obj, Actor *actor) {
|
|
const U6ObjectType *type = get_object_type(obj->obj_n, obj->frame_n, USE_EVENT_SEARCH);
|
|
set_itemref(actor);
|
|
return (uc_event(type, USE_EVENT_SEARCH, obj));
|
|
}
|
|
|
|
|
|
/* Callback from timer or other class. User_data is the object which will
|
|
* receive the message (if applicable). */
|
|
uint16 U6UseCode::callback(uint16 msg, CallBack *caller, void *msg_data) {
|
|
Obj *obj = (Obj *)callback_user_data;
|
|
if (!obj) {
|
|
DEBUG(0, LEVEL_ERROR, "UseCode: internal message %d sent to nullptr object\n", msg);
|
|
return 0;
|
|
}
|
|
return (message_obj(obj, (CallbackMessage)msg, msg_data));
|
|
}
|
|
|
|
|
|
/* Call MESSAGE function for an object. Msg_data is assigned to the appropriate
|
|
* itemref. The USE function is called in response to a DATA_READY message.
|
|
* Returns false if there is no usecode for that object.
|
|
*/
|
|
bool U6UseCode::message_obj(Obj *obj, CallbackMessage msg, void *msg_data) {
|
|
const U6ObjectType *type = get_object_type(obj->obj_n, obj->frame_n, USE_EVENT_MESSAGE);
|
|
items.msg_ref = &msg;
|
|
switch (msg) { // set itemref from msgdata
|
|
case MESG_TIMED:
|
|
items.uint_ref = (uint32 *)msg_data;
|
|
break;
|
|
case MESG_ANIM_HIT_WORLD:
|
|
items.mapcoord_ref = (MapCoord *)msg_data;
|
|
break;
|
|
case MESG_ANIM_HIT:
|
|
items.ent_ref = (MapEntity *)msg_data;
|
|
break;
|
|
case MESG_TEXT_READY:
|
|
items.string_ref = (string *)msg_data;
|
|
break;
|
|
case MESG_DATA_READY:
|
|
items.data_ref = (char *)msg_data; // pointer to EventInput structure
|
|
items.obj_ref = ((EventInput *)items.data_ref)->obj;
|
|
items.actor2_ref = ((EventInput *)items.data_ref)->actor;
|
|
items.mapcoord_ref = ((EventInput *)items.data_ref)->loc;
|
|
items.string_ref = ((EventInput *)items.data_ref)->str;
|
|
return uc_event(get_object_type(obj->obj_n, obj->frame_n),
|
|
USE_EVENT_USE, obj);
|
|
case MESG_INPUT_CANCELED:
|
|
return uc_event(get_object_type(obj->obj_n, obj->frame_n),
|
|
USE_EVENT_INPUT_CANCEL, obj);
|
|
default :
|
|
break;
|
|
}
|
|
|
|
return uc_event(type, USE_EVENT_MESSAGE, obj);
|
|
}
|
|
|
|
|
|
/* MOVE nearby object in a relative direction. */
|
|
bool U6UseCode::move_obj(Obj *obj, sint16 rel_x, sint16 rel_y) {
|
|
const U6ObjectType *type = get_object_type(obj->obj_n, obj->frame_n, USE_EVENT_MOVE);
|
|
static MapCoord dir;
|
|
dir.sx = rel_x;
|
|
dir.sy = rel_y;
|
|
set_itemref(&dir);
|
|
return (uc_event(type, USE_EVENT_MOVE, obj));
|
|
}
|
|
|
|
|
|
/* Call post-LOAD or UNLOAD usecode for an object.
|
|
* Returns false if there is no usecode for that object. */
|
|
bool U6UseCode::load_obj(Obj *obj) {
|
|
const U6ObjectType *type = get_object_type(obj->obj_n, obj->frame_n, USE_EVENT_LOAD);
|
|
return (uc_event(type, USE_EVENT_LOAD, obj));
|
|
}
|
|
|
|
|
|
/* Call READY or UNREADY usecode for an object. */
|
|
bool U6UseCode::ready_obj(Obj *obj, Actor *actor) {
|
|
const U6ObjectType *type = get_object_type(obj->obj_n, obj->frame_n, USE_EVENT_READY);
|
|
set_itemref(actor);
|
|
return (uc_event(type, USE_EVENT_READY, obj));
|
|
}
|
|
|
|
|
|
/* Call GET usecode for an object. */
|
|
bool U6UseCode::get_obj(Obj *obj, Actor *actor) {
|
|
const U6ObjectType *type = get_object_type(obj->obj_n, obj->frame_n, USE_EVENT_GET);
|
|
set_itemref(actor);
|
|
return (uc_event(type, USE_EVENT_GET, obj));
|
|
}
|
|
|
|
|
|
/* Call DROP usecode for an object. */
|
|
bool U6UseCode::drop_obj(Obj *obj, Actor *actor, uint16 x, uint16 y, uint16 qty) {
|
|
const U6ObjectType *type = get_object_type(obj->obj_n, obj->frame_n, USE_EVENT_DROP);
|
|
static MapCoord loc; // use statics for pointers
|
|
static uint32 obj_qty;
|
|
loc.x = x;
|
|
loc.y = y;
|
|
set_itemref(actor);
|
|
set_itemref(&loc);
|
|
items.uint_ref = &obj_qty;
|
|
return (uc_event(type, USE_EVENT_DROP, obj));
|
|
}
|
|
|
|
|
|
/* Return pointer to object-type in list for object N:F, or nullptr if none. */
|
|
inline const U6ObjectType *U6UseCode::get_object_type(uint16 n, uint8 f, UseCodeEvent ev) const {
|
|
const U6ObjectType *type = U6ObjectTypes;
|
|
while (type->obj_n != OBJ_U6_NOTHING) {
|
|
if (type->obj_n == n && (type->frame_n == 0xFF || type->frame_n == f)
|
|
&& ((type->trigger & ev) || ev == 0))
|
|
return type;
|
|
++type;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
/* Call usecode function of the U6ObjectType, with event `ev', for `obj'.
|
|
* The meaning of the return value is different for each event.
|
|
* Returns false if the type is invalid or doesn't respond to the event.
|
|
*/
|
|
bool U6UseCode::uc_event(const U6ObjectType *type, UseCodeEvent ev, Obj *obj) {
|
|
if (!type || type->obj_n == OBJ_U6_NOTHING)
|
|
return false;
|
|
if (type->trigger & ev) {
|
|
dbg_print_event(ev, obj);
|
|
bool ucret = (this->*type->usefunc)(obj, ev);
|
|
clear_items(); // clear references for next call
|
|
return (ucret); // return from usecode function
|
|
}
|
|
return false; // doesn't respond to event
|
|
}
|
|
|
|
|
|
void U6UseCode::lock_door(Obj *obj) {
|
|
if (is_unlocked_door(obj))
|
|
obj->frame_n += 4;
|
|
}
|
|
|
|
void U6UseCode::unlock_door(Obj *obj) {
|
|
if (is_locked_door(obj))
|
|
obj->frame_n -= 4;
|
|
}
|
|
|
|
void U6UseCode::unlock(Obj *obj) {
|
|
if (is_locked_door(obj)) {
|
|
unlock_door(obj);
|
|
} else if (is_locked_chest(obj)) {
|
|
unlock_chest(obj);
|
|
}
|
|
}
|
|
|
|
void U6UseCode::lock(Obj *obj) {
|
|
if (is_magically_locked(obj) || is_locked(obj))
|
|
return;
|
|
|
|
if (is_closed_door(obj)) {
|
|
lock_door(obj);
|
|
} else if (is_closed_chest(obj)) {
|
|
lock_chest(obj);
|
|
}
|
|
}
|
|
|
|
// USE: unlock locked doors, open/close other doors
|
|
bool U6UseCode::use_door(Obj *obj, UseCodeEvent ev) {
|
|
Obj *key_obj;
|
|
bool print = (items.actor_ref == player->get_actor());
|
|
|
|
if (is_magically_locked_door(obj)) {
|
|
if (print) scroll->display_string("\nmagically locked\n");
|
|
return true;
|
|
}
|
|
|
|
if (is_locked_door(obj)) { // locked door
|
|
key_obj = player->get_actor()->inventory_get_object(OBJ_U6_KEY, obj->quality);
|
|
if (obj->quality != 0 && key_obj != nullptr) { // we have the key for this door so lets unlock it.
|
|
unlock_door(obj);
|
|
if (print) scroll->display_string("\nunlocked\n");
|
|
} else if (print) scroll->display_string("\nlocked\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
if (obj->frame_n <= 3) { //the door is open
|
|
if (!map->is_passable(obj->x, obj->y, obj->z) || map->actor_at_location(obj->x, obj->y, obj->z)) { //don't close door if blocked
|
|
if (print) scroll->display_string("\nNot now!\n");
|
|
} else { //close the door
|
|
obj->frame_n += 4;
|
|
if (print) scroll->display_string("\nclosed!\n");
|
|
}
|
|
} else {
|
|
process_effects(obj, items.actor_ref); //process traps.
|
|
obj->frame_n -= 4;
|
|
if (print) scroll->display_string("\nopened!\n");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// USE: climb up or down a ladder (entire party)
|
|
bool U6UseCode::use_ladder(Obj *obj, UseCodeEvent ev) {
|
|
uint16 x = obj->x, y = obj->y;
|
|
uint8 z;
|
|
if (!player->in_party_mode()) {
|
|
scroll->display_string("\nNot in solo mode.\n");
|
|
return true;
|
|
}
|
|
|
|
if (UseCode::out_of_use_range(obj, true))
|
|
return true;
|
|
|
|
if (obj->frame_n == 0) { // DOWN
|
|
if (obj->z == 0) { //handle the transition from the surface to the first dungeon level
|
|
x = (obj->x & 0x07) | (obj->x >> 2 & 0xF8);
|
|
y = (obj->y & 0x07) | (obj->y >> 2 & 0xF8);
|
|
}
|
|
z = obj->z + 1;
|
|
} else { //UP
|
|
if (obj->z == 1) { //we use obj->quality to tell us which surface chunk to come up in.
|
|
x = obj->x / 8 * 8 * 4 + ((obj->quality & 0x03) * 8) + (obj->x - obj->x / 8 * 8);
|
|
y = obj->y / 8 * 8 * 4 + ((obj->quality >> 2 & 0x03) * 8) + (obj->y - obj->y / 8 * 8);
|
|
}
|
|
z = obj->z - 1;
|
|
}
|
|
|
|
party->dismount_from_horses();
|
|
|
|
MapCoord ladder(obj->x, obj->y, obj->z), destination(x, y, z);
|
|
party->walk(&ladder, &destination, 100);
|
|
if (z != 0 && z != 5)
|
|
game->get_weather()->set_wind_dir(NUVIE_DIR_NONE);
|
|
return true;
|
|
}
|
|
|
|
|
|
// USE: Open the passhtrough! Close the passthrough!
|
|
bool U6UseCode::use_passthrough(Obj *obj, UseCodeEvent ev) {
|
|
uint16 new_x, new_y;
|
|
uint8 new_frame_n;
|
|
char action_string[6]; // either 'Open' or 'Close'
|
|
bool print = (items.actor_ref == player->get_actor());
|
|
|
|
new_x = obj->x;
|
|
new_y = obj->y;
|
|
new_frame_n = obj->frame_n;
|
|
|
|
if (obj->frame_n < 2) { // the pass through is currently closed.
|
|
if (obj->obj_n == OBJ_U6_V_PASSTHROUGH)
|
|
new_y--;
|
|
else // OBJ_U6_H_PASSTHROUGH
|
|
new_x--;
|
|
|
|
new_frame_n = 2; // open the pass through
|
|
Common::strcpy_s(action_string, "Open");
|
|
} else { // the pass through is currently open.
|
|
if (obj->obj_n == OBJ_U6_V_PASSTHROUGH)
|
|
new_y++;
|
|
else // OBJ_U6_H_PASSTHROUGH
|
|
new_x++;
|
|
|
|
new_frame_n = 0; // close the pass through
|
|
Common::strcpy_s(action_string, "Close");
|
|
}
|
|
|
|
if (!map->actor_at_location(new_x, new_y, obj->z)) {
|
|
obj_manager->move(obj, new_x, new_y, obj->z);
|
|
obj->frame_n = new_frame_n;
|
|
if (print) {
|
|
scroll->display_string("\n");
|
|
scroll->display_string(action_string);
|
|
scroll->display_string(" the passthrough.\n");
|
|
}
|
|
} else if (print)
|
|
scroll->display_string("\nNot now!\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
// for use with levers and switches, target_obj_n is either OBJ_U6_PORTCULLIS or OBJ_U6_ELECTRIC_FIELD
|
|
bool U6UseCode::use_switch(Obj *obj, UseCodeEvent ev) {
|
|
Obj *doorway_obj;
|
|
Obj *portc_obj;
|
|
U6LList *obj_list;
|
|
U6Link *link;
|
|
uint16 target_obj_n = 0;
|
|
const char *message = nullptr;
|
|
const char *fail_message = nullptr;
|
|
bool success = false;
|
|
bool print = (items.actor_ref == player->get_actor());
|
|
|
|
if (obj->obj_n == OBJ_U6_LEVER) {
|
|
target_obj_n = OBJ_U6_PORTCULLIS;
|
|
message = "\nSwitch the lever, you hear a noise.\n";
|
|
fail_message = "\nSwitch the lever, strange, nothing happened.\n";
|
|
} else if (obj->obj_n == OBJ_U6_SWITCH) {
|
|
if (obj->quality == 113 && obj->x == 139 && obj->y == 0 && obj->z == 1) { // hack for Covetous
|
|
doorway_obj = obj_manager->get_obj_of_type_from_location(OBJ_U6_DOORWAY, 0, 0, 160, 3, 1);
|
|
if (doorway_obj)
|
|
doorway_obj->quality = 113;
|
|
}
|
|
target_obj_n = OBJ_U6_ELECTRIC_FIELD;
|
|
message = "\nOperate the switch, you hear a noise.\n";
|
|
fail_message = "\nOperate the switch, strange, nothing happened.\n";
|
|
}
|
|
|
|
doorway_obj = obj_manager->find_obj(obj->z, OBJ_U6_DOORWAY, obj->quality);
|
|
for (; doorway_obj != nullptr; doorway_obj = obj_manager->find_next_obj(obj->z, doorway_obj)) {
|
|
obj_list = obj_manager->get_obj_list(doorway_obj->x, doorway_obj->y, doorway_obj->z);
|
|
|
|
for (portc_obj = nullptr, link = obj_list->start(); link != nullptr; link = link->next) { // find existing portcullis.
|
|
if (((Obj *)link->data)->obj_n == target_obj_n) {
|
|
portc_obj = (Obj *)link->data;
|
|
break;
|
|
}
|
|
}
|
|
success = true;
|
|
|
|
if (portc_obj == nullptr) { //no barrier object, so lets create one.
|
|
portc_obj = obj_manager->copy_obj(doorway_obj);
|
|
portc_obj->obj_n = target_obj_n;
|
|
portc_obj->quality = 0;
|
|
if (target_obj_n == OBJ_U6_PORTCULLIS) {
|
|
if (portc_obj->frame_n == 9) //FIX Hack for cream buildings might need one for virt wall.
|
|
portc_obj->frame_n = 1;
|
|
} else
|
|
portc_obj->frame_n = 0;
|
|
|
|
obj_manager->add_obj(portc_obj, true);
|
|
} else { //delete barrier object.
|
|
obj_list->remove(portc_obj);
|
|
delete_obj(portc_obj);
|
|
}
|
|
}
|
|
|
|
toggle_frame(obj);
|
|
if (print)
|
|
scroll->display_string(success ? message : fail_message);
|
|
return true;
|
|
}
|
|
|
|
|
|
/* USE: light or douse various fire objects (toggling their frame number)
|
|
*/
|
|
bool U6UseCode::use_firedevice(Obj *obj, UseCodeEvent ev) {
|
|
if (obj->obj_n == OBJ_U6_BRAZIER && obj->frame_n == 2)
|
|
return true; // holy flames can't be doused
|
|
if (obj->obj_n == OBJ_U6_FIREPLACE) {
|
|
if (obj->frame_n == 1 || obj->frame_n == 3) {
|
|
use_firedevice_message(obj, false);
|
|
obj->frame_n--;
|
|
} else {
|
|
use_firedevice_message(obj, true);
|
|
obj->frame_n++;
|
|
}
|
|
} else {
|
|
toggle_frame(obj);
|
|
use_firedevice_message(obj, (bool)obj->frame_n);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/* SEARCH: discover and open door
|
|
* USE: open or close door
|
|
*/
|
|
bool U6UseCode::use_secret_door(Obj *obj, UseCodeEvent ev) {
|
|
if (ev == USE_EVENT_USE) {
|
|
if (obj->frame_n == 1 || obj->frame_n == 3)
|
|
obj->frame_n--;
|
|
else
|
|
obj->frame_n++;
|
|
return true;
|
|
} else if (ev == USE_EVENT_SEARCH) {
|
|
scroll->display_string("a secret door");
|
|
if (obj->frame_n == 0 || obj->frame_n == 2)
|
|
obj->frame_n++;
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/* Use: Open/close container. If container is open, Search.
|
|
* Search: Dump container contents.
|
|
*/
|
|
bool U6UseCode::use_container(Obj *obj, UseCodeEvent ev) {
|
|
if (ev == USE_EVENT_USE) {
|
|
if (is_locked_chest(obj) || is_magically_locked_chest(obj)) {
|
|
if (is_locked_chest(obj) && obj->quality != 0) {
|
|
Obj *key_obj = player->get_actor()->inventory_get_object(OBJ_U6_KEY, obj->quality);
|
|
if (key_obj != nullptr) { // we have the key for this chest so lets unlock it.
|
|
unlock_chest(obj);
|
|
scroll->display_string("\nunlocked\n");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
scroll->display_string("\nNo effect\n");
|
|
return true;
|
|
}
|
|
if ((obj->obj_n == OBJ_U6_CHEST && !obj->is_in_inventory()) || obj->obj_n == OBJ_U6_CRATE || obj->obj_n == OBJ_U6_BARREL)
|
|
toggle_frame(obj); //open / close object
|
|
if (obj->frame_n == 0 || (obj->obj_n != OBJ_U6_CHEST && obj->obj_n != OBJ_U6_CRATE && obj->obj_n != OBJ_U6_BARREL)
|
|
|| (obj->obj_n == OBJ_U6_CHEST && obj->frame_n < 2 && obj->is_in_inventory())) {
|
|
process_effects(obj, items.actor_ref); //run any effects that might be stored in this container. Eg Poison explosion.
|
|
|
|
if (Game::get_game()->doubleclick_opens_containers() && obj->obj_n != OBJ_U6_DEER
|
|
&& obj->obj_n != OBJ_U6_SHIP && obj->obj_n != OBJ_U6_STONE_LION) { // just search for these
|
|
game->get_view_manager()->open_container_view(obj);
|
|
} else if (obj->is_in_inventory()) {
|
|
scroll->display_string("\nNot usable\n");
|
|
return true;
|
|
} else {
|
|
scroll->display_string("\nSearching here, you find ");
|
|
bool found_objects = search_obj(obj, items.actor_ref);
|
|
scroll->display_string(found_objects ? ".\n" : "nothing.\n");
|
|
}
|
|
}
|
|
return true;
|
|
} else if (ev == USE_EVENT_SEARCH) { // search message already printed
|
|
return UseCode::search_container(obj);
|
|
// if(obj->container && obj->container->end())
|
|
// {
|
|
// new TimedContainerSearch(obj);
|
|
// return(true);
|
|
// }
|
|
} else if (ev == USE_EVENT_GET) {
|
|
if (is_chest(obj) && obj->frame_n == 0) //open chest
|
|
obj->frame_n = 1; //close the chest
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Use rune to free shrine */
|
|
bool U6UseCode::use_rune(Obj *obj, UseCodeEvent ev) {
|
|
char mantras[][8] = {"AHM", "MU", "RA", "BEH", "CAH", "SUMM", "OM", "LUM"};
|
|
Obj *force_field = nullptr;
|
|
uint16 rune_obj_offset = obj->obj_n - OBJ_U6_RUNE_HONESTY;
|
|
MapCoord player_location = player->get_actor()->get_location();
|
|
|
|
|
|
scroll->cancel_input_request();
|
|
|
|
if (ev == USE_EVENT_USE) {
|
|
scroll->display_string("Mantra: ");
|
|
|
|
scroll->set_input_mode(true, nullptr, true);
|
|
scroll->request_input(this, obj);
|
|
|
|
return false;
|
|
} else if (ev == USE_EVENT_MESSAGE && items.string_ref) {
|
|
scroll->display_string("\n");
|
|
|
|
size_t mantraSize = items.string_ref->size() + 1;
|
|
char *mantra = new char[mantraSize];
|
|
Common::strcpy_s(mantra, mantraSize, items.string_ref->c_str());
|
|
|
|
if (scumm_stricmp(mantra, mantras[rune_obj_offset]) == 0) {
|
|
// find the matching force field for this shrine. match rune offset against force field quality
|
|
force_field = obj_manager->find_obj(player_location.z, OBJ_U6_FORCE_FIELD, rune_obj_offset);
|
|
|
|
// make sure the player is right next to the force field.
|
|
if (force_field && abs(player_location.x - force_field->x) < 2 && abs(player_location.y - force_field->y) < 2) {
|
|
game->get_sound_manager()->playSfx(NUVIE_SFX_CASTING_MAGIC_P1, SFX_PLAY_ASYNC);
|
|
game->get_sound_manager()->playSfx(NUVIE_SFX_CASTING_MAGIC_P2, SFX_PLAY_SYNC);
|
|
AsyncEffect *e = new AsyncEffect(new XorEffect(1000));
|
|
e->run();
|
|
|
|
remove_gargoyle_egg(force_field->x, force_field->y, force_field->z);
|
|
|
|
obj_manager->remove_obj_from_map(force_field);
|
|
delete force_field;
|
|
|
|
|
|
scroll->display_string("\nDone!\n");
|
|
} else
|
|
scroll->display_string("\nNo effect!\n");
|
|
} else
|
|
scroll->display_string("\nWrong mantra!\n");
|
|
|
|
scroll->display_string("\n");
|
|
scroll->display_prompt();
|
|
delete[] mantra;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void U6UseCode::remove_gargoyle_egg(uint16 x, uint16 y, uint8 z) {
|
|
Std::list<Egg *> *egg_list = game->get_egg_manager()->get_egg_list();
|
|
Std::list<Egg *>::iterator egg_itr;
|
|
|
|
for (egg_itr = egg_list->begin(); egg_itr != egg_list->end();) {
|
|
Egg *egg = *egg_itr;
|
|
Obj *egg_obj = egg->obj;
|
|
egg_itr++; // increment here, it might get removed from the list below.
|
|
if (abs(x - egg_obj->x) < 20 && abs(y - egg_obj->y) < 20 && z == egg_obj->z) {
|
|
if (egg_obj->find_in_container(OBJ_U6_GARGOYLE, 0, false, 0, false) || egg_obj->find_in_container(OBJ_U6_WINGED_GARGOYLE, 0, false, 0, false)) {
|
|
DEBUG(0, LEVEL_DEBUGGING, "Removed egg at (%x,%x,%x)", egg_obj->x, egg_obj->y, egg_obj->z);
|
|
game->get_egg_manager()->remove_egg(egg_obj, false);
|
|
obj_manager->unlink_from_engine(egg_obj);
|
|
delete_obj(egg_obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool U6UseCode::use_vortex_cube(Obj *obj, UseCodeEvent ev) {
|
|
if (ev == USE_EVENT_SEARCH)
|
|
return UseCode::search_container(obj);
|
|
|
|
Obj *britannian_lens, *gargoyle_lens;
|
|
Obj *container_obj;
|
|
Obj *codex;
|
|
U6Link *link;
|
|
uint8 moonstone_check = 0;
|
|
MapCoord player_location = player->get_actor()->get_location();
|
|
codex = obj_manager->find_obj(player_location.z, OBJ_U6_CODEX, 128); // 128 = codex's book id
|
|
|
|
if (Game::get_game()->doubleclick_opens_containers() && (obj->is_in_inventory() || !codex
|
|
|| abs(player_location.x - codex->x) > 11 || abs(player_location.y - codex->y) > 11)) { //FIXME this should probably be mapwindow size)
|
|
game->get_view_manager()->open_container_view(obj);
|
|
return true;
|
|
}
|
|
if (obj->container != nullptr || player_location.z == 0) { // make sure we've got all 8 moonstones in our vortex cube.
|
|
britannian_lens = obj_manager->find_obj(player_location.z, OBJ_U6_BRITANNIAN_LENS, 0, OBJ_NOMATCH_QUALITY);
|
|
gargoyle_lens = obj_manager->find_obj(player_location.z, OBJ_U6_GARGOYLE_LENS, 0, OBJ_NOMATCH_QUALITY);
|
|
|
|
// make sure the player is close to the codex
|
|
if (codex && abs(player_location.x - codex->x) < 11 && abs(player_location.y - codex->y) < 11) { //FIXME this should probably be mapwindow size
|
|
// check that the lenses are in the correct place.
|
|
if (britannian_lens && gargoyle_lens &&
|
|
britannian_lens->x == 0x399 && britannian_lens->y == 0x353 && britannian_lens->z == 0 &&
|
|
gargoyle_lens->x == 0x39d && gargoyle_lens->y == 0x353 && gargoyle_lens->z == 0) {
|
|
for (link = obj->container->start(); link != nullptr; link = link->next) {
|
|
container_obj = (Obj *)link->data;
|
|
if (container_obj->obj_n == OBJ_U6_MOONSTONE) {
|
|
moonstone_check |= 1 << container_obj->frame_n;
|
|
}
|
|
}
|
|
|
|
if (moonstone_check == 0xff) { // have we got all 8 moonstones?
|
|
obj_manager->remove_obj_from_map(codex);
|
|
delete codex;
|
|
|
|
scroll->display_string("\nThe Codex has vanished!\n");
|
|
|
|
//FIXME put weird swirl effect in here.
|
|
|
|
game->get_map_window()->Hide();
|
|
game->get_scroll()->Hide();
|
|
game->get_background()->Hide();
|
|
game->get_command_bar()->Hide();
|
|
game->get_event()->close_gumps();
|
|
if (game->get_view_manager()->get_current_view())
|
|
game->get_view_manager()->get_current_view()->Hide();
|
|
|
|
game->get_script()->play_cutscene("/ending.lua");
|
|
game->quit();
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DEBUG(0, LEVEL_DEBUGGING, "moonstone_check = %d\n", moonstone_check);
|
|
scroll->display_string("\nNo Effect!\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/* Use bell or pull-chain, ring and animate nearby bell.
|
|
*/
|
|
bool U6UseCode::use_bell(Obj *obj, UseCodeEvent ev) {
|
|
Obj *bell = nullptr;
|
|
if (ev != USE_EVENT_USE)
|
|
return false;
|
|
if (obj->obj_n == OBJ_U6_BELL)
|
|
bell = obj;
|
|
else
|
|
bell = bell_find(obj);
|
|
if (bell) {
|
|
obj_manager->animate_forwards(bell, 2);
|
|
}
|
|
Game::get_game()->get_sound_manager()->playSfx(NUVIE_SFX_BELL);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/* Find bell near its pull-chain.
|
|
*/
|
|
Obj *U6UseCode::bell_find(Obj *chain_obj) {
|
|
Obj *bell = nullptr;
|
|
for (uint16 x = chain_obj->x - 8; x <= chain_obj->x + 8; x++)
|
|
for (uint16 y = chain_obj->y - 8; y <= chain_obj->y + 8 && !bell; y++)
|
|
bell = obj_manager->get_obj_of_type_from_location(OBJ_U6_BELL, x, y, chain_obj->z);
|
|
return bell;
|
|
}
|
|
|
|
|
|
//cranks control drawbridges.
|
|
bool U6UseCode::use_crank(Obj *obj, UseCodeEvent ev) {
|
|
uint16 x, y;
|
|
uint8 level;
|
|
uint16 b_width;
|
|
bool bridge_open;
|
|
Obj *start_obj;
|
|
|
|
start_obj = drawbridge_find(obj);
|
|
|
|
if (start_obj->frame_n == 3) // bridge open
|
|
bridge_open = true;
|
|
else
|
|
bridge_open = false;
|
|
|
|
x = start_obj->x;
|
|
y = start_obj->y;
|
|
level = start_obj->z;
|
|
drawbridge_remove(x, y, level, &b_width);
|
|
|
|
// find and animate chain
|
|
if (!(start_obj = obj_manager->get_obj_of_type_from_location(OBJ_U6_CHAIN, obj->x + 1, obj->y, obj->z)))
|
|
start_obj = obj_manager->get_obj_of_type_from_location(OBJ_U6_CHAIN, obj->x - 1, obj->y, obj->z);
|
|
if (start_obj)
|
|
obj_manager->animate_forwards(start_obj, 3);
|
|
|
|
if (bridge_open) {
|
|
obj_manager->animate_backwards(obj, 3); // animate crank
|
|
drawbridge_close(x, y, level, b_width);
|
|
} else {
|
|
obj_manager->animate_forwards(obj, 3);
|
|
drawbridge_open(x, y, level, b_width);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Obj *U6UseCode::drawbridge_find(Obj *crank_obj) {
|
|
uint16 i, j;
|
|
Obj *start_obj, *tmp_obj;
|
|
|
|
for (i = 0; i < 6; i++) { // search on right side of crank.
|
|
start_obj = obj_manager->get_obj_of_type_from_location(OBJ_U6_DRAWBRIDGE, crank_obj->x + 1, crank_obj->y + i, crank_obj->z);
|
|
if (start_obj != nullptr) // this means we are using the left crank.
|
|
return start_obj;
|
|
}
|
|
|
|
for (i = 0; i < 6; i++) { // search on left side of crank.
|
|
tmp_obj = obj_manager->get_obj_of_type_from_location(OBJ_U6_DRAWBRIDGE, crank_obj->x - 1, crank_obj->y + i, crank_obj->z);
|
|
|
|
if (tmp_obj != nullptr) { // this means we are using the right crank.
|
|
//find the start of the drawbridge on the left.
|
|
// we do this by searching to the left of the crank till we hit the crank on the otherside.
|
|
// we then move right 1 tile and down 'i' tiles to the start object. :)
|
|
for (j = 1; j < crank_obj->x; j++) {
|
|
tmp_obj = obj_manager->get_obj_of_type_from_location(OBJ_U6_CRANK, crank_obj->x - j, crank_obj->y, crank_obj->z);
|
|
if (tmp_obj && tmp_obj->obj_n == OBJ_U6_CRANK) {
|
|
start_obj = obj_manager->get_obj_of_type_from_location(OBJ_U6_DRAWBRIDGE, tmp_obj->x + 1, tmp_obj->y + i, tmp_obj->z);
|
|
return start_obj;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void U6UseCode::drawbridge_open(uint16 x, uint16 y, uint8 level, uint16 b_width) {
|
|
uint16 i, j;
|
|
Obj *obj;
|
|
|
|
y++;
|
|
|
|
for (i = 0;; i++) {
|
|
obj = new_obj(OBJ_U6_DRAWBRIDGE, 3, x, y + i, level); //left side chain
|
|
obj_manager->add_obj(obj, true);
|
|
|
|
obj = new_obj(OBJ_U6_DRAWBRIDGE, 5, x + b_width - 1, y + i, level); //right side chain
|
|
obj_manager->add_obj(obj, true);
|
|
|
|
for (j = 0; j < b_width - 2; j++) {
|
|
obj = new_obj(OBJ_U6_DRAWBRIDGE, 4, x + 1 + j, y + i, level);
|
|
obj_manager->add_obj(obj, true);
|
|
}
|
|
|
|
if (map->is_passable(x, y + i + 1, level)) //we extend the drawbridge until we hit a passable tile.
|
|
break;
|
|
}
|
|
|
|
i++;
|
|
|
|
for (j = 0; j < b_width - 2; j++) { //middle bottom tiles
|
|
obj = new_obj(OBJ_U6_DRAWBRIDGE, 1, x + 1 + j, y + i, level);
|
|
obj_manager->add_obj(obj, true);
|
|
}
|
|
|
|
obj = new_obj(OBJ_U6_DRAWBRIDGE, 0, x, y + i, level); //bottom left
|
|
obj_manager->add_obj(obj, true);
|
|
|
|
obj = new_obj(OBJ_U6_DRAWBRIDGE, 2, x + b_width - 1, y + i, level); // bottom right
|
|
obj_manager->add_obj(obj, true);
|
|
|
|
scroll->display_string("\nOpen the drawbridge.\n");
|
|
|
|
return;
|
|
}
|
|
|
|
void U6UseCode::drawbridge_close(uint16 x, uint16 y, uint8 level, uint16 b_width) {
|
|
uint16 i;
|
|
Obj *obj;
|
|
|
|
y--;
|
|
|
|
obj = new_obj(OBJ_U6_DRAWBRIDGE, 6, x - 1, y, level); //left side
|
|
obj_manager->add_obj(obj, true);
|
|
|
|
obj = new_obj(OBJ_U6_DRAWBRIDGE, 8, x + b_width - 1, y, level); //right side
|
|
obj_manager->add_obj(obj, true);
|
|
|
|
for (i = 0; i < b_width - 1; i++) {
|
|
obj = new_obj(OBJ_U6_DRAWBRIDGE, 7, x + i, y, level); //middle
|
|
obj_manager->add_obj(obj, true);
|
|
}
|
|
|
|
scroll->display_string("\nClose the drawbridge.\n");
|
|
}
|
|
|
|
/* GET: clear buried moonstone location
|
|
* (we never know if it is buried or just lying there)
|
|
* USE: drop at user's current location, and update buried location
|
|
*/
|
|
bool U6UseCode::use_moonstone(Obj *obj, UseCodeEvent ev) {
|
|
if (ev == USE_EVENT_GET) {
|
|
Weather *weather = game->get_weather();
|
|
/* FIXME: need to check weights here already?
|
|
* Check original's behavior when moonstone cannot be gotten due
|
|
* to weight limitations. Moving it normally doesn't unbury it,
|
|
* so probably failing to get it shouldn't either.
|
|
*/
|
|
weather->set_moonstone(obj->frame_n, MapCoord(0, 0, 0)) ;
|
|
//scroll->display_string("\nMoonstone dug up. (FIXME)\n");
|
|
weather->update_moongates();
|
|
return true;
|
|
} else if (ev == USE_EVENT_USE) {
|
|
Weather *weather = game->get_weather();
|
|
MapCoord loc = Game::get_game()->get_player()->get_actor()->get_location();
|
|
const Tile *map_tile = map->get_tile(loc.x, loc.y, loc.z);
|
|
|
|
if ((map_tile->tile_num < 1 || map_tile->tile_num > 7) && (map_tile->tile_num < 0x10 || map_tile->tile_num > 0x6f)) {
|
|
scroll->display_string("Cannot be buried here!\n");
|
|
return true;
|
|
}
|
|
|
|
weather->set_moonstone(obj->frame_n, loc) ;
|
|
scroll->display_string("buried.\n");
|
|
|
|
obj_manager->moveto_map(obj, loc);
|
|
obj->status |= OBJ_STATUS_OK_TO_TAKE;
|
|
weather->update_moongates();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* USE: select location on ground to bury orb; when location is passed, open
|
|
* a red moongate to a new location
|
|
*/
|
|
bool U6UseCode::use_orb(Obj *obj, UseCodeEvent ev) {
|
|
Obj *gate;
|
|
uint16 x, y, ox, oy;
|
|
uint8 px, py, z, oz;
|
|
uint8 position;
|
|
Actor *lord_british;
|
|
MapCoord *mapcoord_ref = items.mapcoord_ref;
|
|
|
|
// can't use obj->is_on_map() since new container gumps will
|
|
// have corpse items in an npc's inventoty
|
|
if (!party->has_obj(87, 0, false)) { // make sure orb of moons is in party inventory
|
|
scroll->display_string("\nNot usable\n");
|
|
return true;
|
|
}
|
|
|
|
player->get_actor()->get_location(&x, &y, &z);
|
|
lord_british = actor_manager->get_actor(U6_LORD_BRITISH_ACTOR_NUM);
|
|
|
|
// The player must ask Lord British about the orb before it can be used.
|
|
// This sets the flag 0x20 in LB's flags field which allows the orb to be used.
|
|
|
|
if ((lord_british->get_talk_flags() & U6_LORD_BRITISH_ORB_CHECK_FLAG) == 0) {
|
|
scroll->display_string("\nYou can't figure out how to use it.\n");
|
|
return true;
|
|
}
|
|
|
|
// if(!orb_activated)
|
|
// {
|
|
// scroll->display_string("\nYou must recharge it first!\n");
|
|
// return true;
|
|
// }
|
|
|
|
if (ev == USE_EVENT_INPUT_CANCEL ||
|
|
(items.obj_ref && !items.obj_ref->is_on_map())) { // selected item in inventory
|
|
scroll->display_string("Failed\n");
|
|
return true;
|
|
}
|
|
|
|
if (!mapcoord_ref) {
|
|
game->get_event()->get_target(MapCoord(x, y, z), "Where: ");
|
|
game->get_event()->request_input(this, obj);
|
|
return false; // no prompt
|
|
}
|
|
|
|
ox = mapcoord_ref->x;
|
|
oy = mapcoord_ref->y;
|
|
oz = mapcoord_ref->z;
|
|
|
|
px = 3 + ox - x;
|
|
py = 2 + oy - y;
|
|
|
|
if (px > 5 || py > 4 || // Moongate out of range.
|
|
items.actor2_ref || // Actor at location.
|
|
!map->is_passable(ox, oy, oz)) { // Location not passable.
|
|
scroll->display_string("Failed.\n");
|
|
return true;
|
|
}
|
|
|
|
position = px + py * 5;
|
|
|
|
if (position >= 12 && position <= 14) // The three middle positions go noware.
|
|
position = 0;
|
|
|
|
gate = new_obj(OBJ_U6_RED_GATE, 1, ox, oy, z);
|
|
gate->quality = position;
|
|
gate->set_temporary();
|
|
|
|
new VanishEffect(VANISH_WAIT);
|
|
obj_manager->add_obj(gate, true);
|
|
game->get_map_window()->updateBlacking(); // next update not until Effect completes
|
|
scroll->display_string("a red moon gate appears.\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void U6UseCode::drawbridge_remove(uint16 x, uint16 y, uint8 level, uint16 *bridge_width) {
|
|
uint16 w, h;
|
|
|
|
//remove end of closed drawbridge.
|
|
// if present.
|
|
if (x > 0)
|
|
obj_manager->remove_obj_type_from_location(OBJ_U6_DRAWBRIDGE, x - 1, y, level);
|
|
|
|
*bridge_width = 0;
|
|
|
|
//remove the rest of the bridge.
|
|
for (h = 0, w = 1; w != 0; h++) {
|
|
for (w = 0;; w++) {
|
|
if (obj_manager->remove_obj_type_from_location(OBJ_U6_DRAWBRIDGE, x + w, y + h, level) == false) {
|
|
if (w != 0)
|
|
*bridge_width = w;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// USE: fishing pole. Attempt to catch a fish from an adjacent water square.
|
|
bool U6UseCode::use_fishing_pole(Obj *obj, UseCodeEvent ev) {
|
|
ViewManager *view_manager = game->get_view_manager();
|
|
Actor *player_actor;
|
|
Obj *fish;
|
|
uint16 x, y;
|
|
uint8 z;
|
|
|
|
player_actor = player->get_actor();
|
|
|
|
player_actor->get_location(&x, &y, &z);
|
|
|
|
if (use_find_water(&x, &y, &z) == false) {
|
|
scroll->display_string("\nYou need to stand next to water.\n");
|
|
return true;
|
|
}
|
|
|
|
if (NUVIE_RAND() % 100 <= 20) {
|
|
fish = new Obj();
|
|
|
|
fish->obj_n = OBJ_U6_FISH;
|
|
if (!player_actor->can_carry_object(fish)) {
|
|
scroll->display_string("\nGot it, but can't carry it.\n");
|
|
if (use_boat_find_land(&x, &y, &z) == false) { //we couldn't find an empty spot for the fish.
|
|
//so back into the water with thee.
|
|
delete fish;
|
|
return true;
|
|
}
|
|
|
|
fish->x = x;
|
|
fish->y = y;
|
|
fish->z = z;
|
|
fish->set_ok_to_take(true);
|
|
obj_manager->add_obj(fish);
|
|
return true;
|
|
}
|
|
|
|
player_actor->inventory_add_object(fish);
|
|
|
|
if (!game->is_new_style())
|
|
view_manager->set_inventory_mode();
|
|
|
|
view_manager->update(); //FIX this should be moved higher up in UseCode
|
|
|
|
scroll->display_string("\nGot it!\n");
|
|
} else
|
|
scroll->display_string("\nDidn't get a fish.\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
inline bool U6UseCode::use_find_water(uint16 *x, uint16 *y, uint8 *z) {
|
|
if (map->is_water(*x, *y - 1, *z)) { //UP
|
|
*y = *y - 1;
|
|
return true;
|
|
}
|
|
if (map->is_water(*x + 1, *y, *z)) { //RIGHT
|
|
*x = *x + 1;
|
|
return true;
|
|
}
|
|
if (map->is_water(*x, *y + 1, *z)) { //DOWN
|
|
*y = *y + 1;
|
|
return true;
|
|
}
|
|
if (map->is_water(*x - 1, *y, *z)) { //LEFT
|
|
*x = *x - 1;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/* Use shovel in one of 8 directions. If used in a dungeon level get a chance of
|
|
* finding gold or a fountain (to make a wish).
|
|
*/
|
|
bool U6UseCode::use_shovel(Obj *obj, UseCodeEvent ev) {
|
|
Obj *dug_up_obj = nullptr;
|
|
Obj *ladder_obj;
|
|
MapCoord from, dig_at, ladder;
|
|
|
|
if (ev == USE_EVENT_INPUT_CANCEL) {
|
|
scroll->display_string("nowhere.\n");
|
|
return true;
|
|
}
|
|
|
|
if (!items.mapcoord_ref) { // get direction (FIXME: should return relative dir)
|
|
if (!obj->is_readied()) {
|
|
scroll->display_string("\nNot readied.\n");
|
|
return true;
|
|
}
|
|
if (items.actor_ref == nullptr) { // happens when you use on a widget
|
|
scroll->display_string("nowhere.\n");
|
|
return true;
|
|
}
|
|
Actor *parent = obj->get_actor_holding_obj();
|
|
from = parent->get_location();
|
|
|
|
// game->get_event()->useselect_mode(obj, "Direction: ");
|
|
game->get_event()->get_direction(from, "Direction: ");
|
|
if (game->get_map_window()->get_interface() == INTERFACE_NORMAL)
|
|
game->get_event()->do_not_show_target_cursor = true;
|
|
game->get_event()->request_input(this, obj);
|
|
return false;
|
|
}
|
|
|
|
Actor *parent = obj->get_actor_holding_obj();
|
|
from = parent->get_location();
|
|
|
|
dig_at = *items.mapcoord_ref;
|
|
|
|
if (game->get_map_window()->get_interface() < INTERFACE_FULLSCREEN) {
|
|
dig_at.sx = (dig_at.sx == 0) ? 0 : (dig_at.sx < 0) ? -1 : 1;
|
|
dig_at.sy = (dig_at.sy == 0) ? 0 : (dig_at.sy < 0) ? -1 : 1;
|
|
}
|
|
|
|
scroll->display_string(get_direction_name(dig_at.x, dig_at.y));
|
|
if (dig_at.sx == 0 && dig_at.sy == 0) {
|
|
scroll->display_string(".\n");
|
|
return true; // display prompt
|
|
}
|
|
scroll->display_string(".\n\n");
|
|
|
|
dig_at.x += from.x;
|
|
dig_at.y += from.y;
|
|
dig_at.z = from.z;
|
|
if (!dig_at.is_visible()) {
|
|
scroll->display_string("Not on screen.\n");
|
|
return true; // display prompt
|
|
} else if (!from.is_visible() && from.distance(dig_at) > 5) {
|
|
scroll->display_string("Out of range.\n");
|
|
return true; // display prompt
|
|
} else if (game->get_map_window()->get_interface() != INTERFACE_IGNORE_BLOCK) {
|
|
LineTestResult lt;
|
|
if (map->lineTest(from.x, from.y, dig_at.x, dig_at.y, dig_at.z, LT_HitUnpassable, lt)) {
|
|
MapCoord hit_loc = MapCoord(lt.hit_x, lt.hit_y, lt.hit_level);
|
|
if (hit_loc != dig_at) {
|
|
scroll->display_string("Blocked\n");
|
|
return true; // display prompt
|
|
}
|
|
}
|
|
}
|
|
Obj *hole = obj_manager->get_obj_of_type_from_location(OBJ_U6_HOLE, dig_at.x, dig_at.y, dig_at.z);
|
|
if (hole || dig_at.z == 5 // we can't go anywhere from the gargoyle world.
|
|
|| game->get_map_window()->tile_is_black(dig_at.x, dig_at.y)
|
|
|| (dig_at.z == 0 && (dig_at.x != 0x2c3 || dig_at.y != 0x343))) {
|
|
scroll->display_string("No effect\n");
|
|
return true; // ??
|
|
}
|
|
|
|
// try to conenct with a ladder on a lower level.
|
|
ladder = dig_at;
|
|
// This is to inacurate. It will detect an extra ladder 8 tiles east
|
|
// plus another one 8 tiles north and 8 tiles east
|
|
if (dig_at.z == 0) { //handle the transition from the surface to the first dungeon level
|
|
ladder.x = (dig_at.x & 0x07) | (dig_at.x >> 2 & 0xF8);
|
|
ladder.y = (dig_at.y & 0x07) | (dig_at.y >> 2 & 0xF8);
|
|
}
|
|
|
|
ladder.z = dig_at.z + 1;
|
|
|
|
// if(dig_at.z != 5) already checked
|
|
{
|
|
ladder_obj = obj_manager->get_obj_of_type_from_location(OBJ_U6_LADDER, ladder.x, ladder.y, ladder.z);
|
|
if (ladder_obj && ladder_obj->frame_n == 1) { // ladder up.
|
|
scroll->display_string("You dig a hole.\n");
|
|
dug_up_obj = new_obj(OBJ_U6_HOLE, 0, dig_at.x, dig_at.y, dig_at.z); //found a connecting ladder, dig a hole
|
|
}
|
|
}
|
|
const Tile *tile = map->get_tile(dig_at.x, dig_at.y, dig_at.z, true);
|
|
// uncomment first check if the coord conversion gets added back
|
|
if (/*(!dug_up_obj && dig_at.z == 0) ||*/ !tile // original might have checked for earth desc and no wall mask
|
|
|| !((tile->tile_num <= 111 && tile->tile_num >= 108) || tile->tile_num == 540)) {
|
|
scroll->display_string("No Effect.\n");
|
|
return true;
|
|
}
|
|
|
|
if (!dug_up_obj) {
|
|
// 10% chance of anything
|
|
if (NUVIE_RAND() % 10) {
|
|
scroll->display_string("Failed\n");
|
|
return true;
|
|
}
|
|
|
|
// Door #1 or Door #2?
|
|
Obj *fountain = obj_manager->get_obj_of_type_from_location(OBJ_U6_FOUNTAIN, dig_at.x, dig_at.y, dig_at.z);
|
|
if ((NUVIE_RAND() % 2)) { // original lets you stack fountains
|
|
scroll->display_string("You find a water fountain.\n");
|
|
if (!fountain) // don't actually add another one
|
|
dug_up_obj = new_obj(OBJ_U6_FOUNTAIN, 0, dig_at.x, dig_at.y, dig_at.z);
|
|
} else {
|
|
scroll->display_string("You find a gold nugget.\n");
|
|
dug_up_obj = new_obj(OBJ_U6_GOLD_NUGGET, 0, dig_at.x, dig_at.y, dig_at.z);
|
|
dug_up_obj->status |= OBJ_STATUS_OK_TO_TAKE;
|
|
}
|
|
}
|
|
if (dug_up_obj) {
|
|
dug_up_obj->set_temporary();
|
|
obj_manager->add_obj(dug_up_obj, true);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/* USE: Magic fountain. Make a wish!
|
|
*/
|
|
bool U6UseCode::use_fountain(Obj *obj, UseCodeEvent ev) {
|
|
static bool get_wish = false;
|
|
static Actor *wish_actor = nullptr; // person receiving gift
|
|
|
|
scroll->cancel_input_request();
|
|
if (ev == USE_EVENT_USE) {
|
|
scroll->display_string("Make a wish? ");
|
|
// get Y/N single char, no ENTER (FIXME: no printing)
|
|
scroll->set_input_mode(true, "yn", false);
|
|
scroll->request_input(this, obj);
|
|
wish_actor = items.actor_ref;
|
|
assert(wish_actor);
|
|
return false;
|
|
} else if (ev == USE_EVENT_MESSAGE && items.string_ref) {
|
|
scroll->display_string("\n");
|
|
if (!get_wish) { // answered with Y/N
|
|
// Y:
|
|
if (*items.string_ref == "y" || *items.string_ref == "Y") {
|
|
scroll->display_string("Wish for: ");
|
|
// get string
|
|
scroll->set_input_mode(true);
|
|
scroll->request_input(this, obj);
|
|
get_wish = true;
|
|
} else { // N: won't wish
|
|
scroll->display_string("\n");
|
|
scroll->display_prompt();
|
|
}
|
|
} else { // answered with wish
|
|
get_wish = false;
|
|
bool wished_for_food = false;
|
|
size_t wishSize = items.string_ref->size() + 1;
|
|
char *wish = (char *)malloc(wishSize);
|
|
Common::strcpy_s(wish, wishSize, items.string_ref->c_str());
|
|
if (scumm_stricmp(wish, "Food") == 0 || scumm_stricmp(wish, "Mutton") == 0
|
|
|| scumm_stricmp(wish, "Wine") == 0 || scumm_stricmp(wish, "Fruit") == 0
|
|
|| scumm_stricmp(wish, "Mead") == 0)
|
|
wished_for_food = true;
|
|
free(wish);
|
|
if (!wished_for_food) {
|
|
scroll->display_string("\nFailed\n\n");
|
|
scroll->display_prompt();
|
|
return true;
|
|
}
|
|
// 25% chance of anything
|
|
if ((NUVIE_RAND() % 4) != 0) {
|
|
scroll->display_string("\nNo effect\n\n");
|
|
scroll->display_prompt();
|
|
return true;
|
|
}
|
|
scroll->display_string("\nYou got food");
|
|
// must be able to carry it
|
|
if (!wish_actor->can_carry_object(OBJ_U6_MEAT_PORTION, 1)) {
|
|
scroll->display_string(", but you can't carry it.\n\n");
|
|
scroll->display_prompt();
|
|
return true;
|
|
}
|
|
scroll->display_string(".\n\n");
|
|
scroll->display_prompt();
|
|
assert(wish_actor);
|
|
wish_actor->inventory_new_object(OBJ_U6_MEAT_PORTION, 1);
|
|
}
|
|
} else
|
|
get_wish = false;
|
|
return false;
|
|
}
|
|
|
|
|
|
/* USE: Make a rubber ducky sound. */
|
|
bool U6UseCode::use_rubber_ducky(Obj *obj, UseCodeEvent ev) {
|
|
if (items.actor_ref == player->get_actor())
|
|
scroll->display_string("\nSqueak!\n");
|
|
Game::get_game()->get_sound_manager()->playSfx(NUVIE_SFX_RUBBER_DUCK); //FIXME towns says "Quack! Quack!" and plays sfx twice.
|
|
return true;
|
|
}
|
|
|
|
sint16 U6UseCode::parseLatLongString(U6UseCodeLatLonEnum mode, Std::string *input) {
|
|
uint16 len = input->size();
|
|
sint16 val = 0;
|
|
for (uint16 i = 0; i < len; i++) {
|
|
char c = (*input)[i];
|
|
if (c < '0' || c > '9') {
|
|
c = toupper(c);
|
|
if (mode == LAT) {
|
|
if (c == 'N' || c == 'S') {
|
|
if (c == 'N')
|
|
val = -val;
|
|
} else {
|
|
val = 100;
|
|
}
|
|
} else {
|
|
if (c == 'E' || c == 'W') {
|
|
if (c == 'W')
|
|
val = -val;
|
|
} else {
|
|
val = 100;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
val = val * 10 + (*input)[i] - 48;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
/* USE: Crystal ball
|
|
*/
|
|
bool U6UseCode::use_crystal_ball(Obj *obj, UseCodeEvent ev) {
|
|
static enum { GET_LAT, GET_LON} mode = GET_LAT;
|
|
static MapCoord loc;
|
|
static Actor *actor = nullptr;
|
|
|
|
scroll->cancel_input_request();
|
|
if (ev == USE_EVENT_USE) {
|
|
actor = items.actor_ref;
|
|
if ((int)NUVIE_RAND() % 30 < (45 - actor->get_intelligence()) / 2) { //use crystal ball saving roll.
|
|
game->get_script()->call_actor_hit(actor, (NUVIE_RAND() % 10) + 1, SCRIPT_DISPLAY_HIT_MSG);
|
|
scroll->display_string("\n");
|
|
scroll->display_prompt();
|
|
return false;
|
|
}
|
|
|
|
mode = GET_LAT;
|
|
scroll->display_string("Enter degrees followed by N, S, E or W.\n\nAt latitude=");
|
|
scroll->set_input_mode(true);
|
|
scroll->request_input(this, obj);
|
|
|
|
return false;
|
|
} else if (ev == USE_EVENT_MESSAGE && items.string_ref) {
|
|
if (mode == GET_LAT) {
|
|
sint16 lat = parseLatLongString(LAT, items.string_ref);
|
|
if (lat > 80 || lat < -44) {
|
|
scroll->display_string("\n\n");
|
|
scroll->display_prompt();
|
|
return false;
|
|
}
|
|
|
|
loc.y = lat * 8 + 360;
|
|
scroll->display_string("\n");
|
|
scroll->display_string(" longitude=");
|
|
scroll->set_input_mode(true);
|
|
scroll->request_input(this, obj);
|
|
mode = GET_LON;
|
|
} else if (mode == GET_LON) {
|
|
scroll->display_string("\n");
|
|
sint16 lon = parseLatLongString(LON, items.string_ref);
|
|
if (lon > 88 || lon < -37) {
|
|
scroll->display_string("\n\n");
|
|
scroll->display_prompt();
|
|
return false;
|
|
}
|
|
|
|
loc.x = lon * 8 + 304;
|
|
|
|
actor->get_location(nullptr, nullptr, &loc.z);
|
|
if (loc.z != 0) {
|
|
loc.x = loc.x / 4;
|
|
loc.y = loc.y / 4;
|
|
}
|
|
|
|
AsyncEffect *e = new AsyncEffect(new WizardEyeEffect(loc, 0x28));
|
|
e->run(EFFECT_PROCESS_GUI_INPUT);
|
|
scroll->display_string("\nDone\n\n");
|
|
scroll->display_prompt();
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* USE: Enter instrument playing mode, with sound for used object. */
|
|
bool U6UseCode::play_instrument(Obj *obj, UseCodeEvent ev) {
|
|
// FIXME: need instrument sounds AND a config option to simply change music
|
|
// track when an instrument is played. Maybe NORTH_KEY and SOUTH_KEY can cycle through sounds/music and DO_ACTION_KEY can play it.
|
|
/// FIXME: also some floating music note icons like in U7
|
|
game->get_event()->close_gumps(); // gumps will steal input
|
|
const char *musicmsg = (obj->obj_n == OBJ_U6_PANPIPES) ? "panpipes"
|
|
: (obj->obj_n == OBJ_U6_HARPSICHORD) ? "harpsichord"
|
|
: (obj->obj_n == OBJ_U6_HARP) ? "harp"
|
|
: (obj->obj_n == OBJ_U6_LUTE) ? "lute"
|
|
: (obj->obj_n == OBJ_U6_XYLOPHONE) ? "xylophone"
|
|
: "musical instrument";
|
|
if (items.data_ref) {
|
|
Common::KeyCode key = ((EventInput *)items.data_ref)->key;
|
|
ActionKeyType key_type = ((EventInput *)items.data_ref)->action_key_type;
|
|
if (key == Common::KEYCODE_0) DEBUG(0, LEVEL_WARNING, "FIXME: %s: modulate 0\n", musicmsg);
|
|
if (key == Common::KEYCODE_1) DEBUG(0, LEVEL_WARNING, "FIXME: %s: modulate 1\n", musicmsg);
|
|
if (key == Common::KEYCODE_2) DEBUG(0, LEVEL_WARNING, "FIXME: %s: modulate 2\n", musicmsg);
|
|
if (key == Common::KEYCODE_3) DEBUG(0, LEVEL_WARNING, "FIXME: %s: modulate 3\n", musicmsg);
|
|
if (key == Common::KEYCODE_4) DEBUG(0, LEVEL_WARNING, "FIXME: %s: modulate 4\n", musicmsg);
|
|
if (key == Common::KEYCODE_5) DEBUG(0, LEVEL_WARNING, "FIXME: %s: modulate 5\n", musicmsg);
|
|
if (key == Common::KEYCODE_6) DEBUG(0, LEVEL_WARNING, "FIXME: %s: modulate 6\n", musicmsg);
|
|
if (key == Common::KEYCODE_7) DEBUG(0, LEVEL_WARNING, "FIXME: %s: modulate 7\n", musicmsg);
|
|
if (key == Common::KEYCODE_8) DEBUG(0, LEVEL_WARNING, "FIXME: %s: modulate 8\n", musicmsg);
|
|
if (key == Common::KEYCODE_9) DEBUG(0, LEVEL_WARNING, "FIXME: %s: modulate 9\n", musicmsg);
|
|
return (key_type != DO_ACTION_KEY && key_type != CANCEL_ACTION_KEY);
|
|
} else
|
|
game->get_event()->key_redirect(this, obj);
|
|
return false;
|
|
}
|
|
|
|
|
|
// use_firedevice()
|
|
bool U6UseCode::use_firedevice_message(Obj *obj, bool lit) {
|
|
if (items.actor_ref != player->get_actor())
|
|
return true;
|
|
scroll->display_string("\n");
|
|
scroll->display_string(obj_manager->get_obj_name(obj));
|
|
if (lit)
|
|
scroll->display_string(" is lit.\n");
|
|
else
|
|
scroll->display_string(" is doused.\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
/* USE: Eat/drink food object. Hic!
|
|
*/
|
|
bool U6UseCode::use_food(Obj *obj, UseCodeEvent ev) {
|
|
if (ev == USE_EVENT_USE) {
|
|
if (items.actor_ref == player->get_actor()) {
|
|
if (obj->obj_n == OBJ_U6_WINE || obj->obj_n == OBJ_U6_MEAD
|
|
|| obj->obj_n == OBJ_U6_ALE) {
|
|
scroll->display_string("\nYou drink it.\n");
|
|
|
|
player->add_alcohol(); // add to drunkenness
|
|
} else
|
|
scroll->display_string("\nYou eat the food.\n");
|
|
}
|
|
destroy_obj(obj, 1);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/* USE: Use potion. If actor2 is passed, give them the potion, else select
|
|
* actor2. */
|
|
bool U6UseCode::use_potion(Obj *obj, UseCodeEvent ev) {
|
|
ActorManager *am = actor_manager;
|
|
|
|
if (ev == USE_EVENT_USE) {
|
|
if (!items.actor2_ref && !items.obj_ref && !items.mapcoord_ref) {
|
|
game->get_event()->get_target(items.actor_ref->get_location(), "On whom: ");
|
|
game->get_event()->request_input(this, obj);
|
|
} else if (!items.actor2_ref) { // no selection
|
|
scroll->display_string("nobody\n");
|
|
return true;
|
|
} else { // use potion
|
|
sint8 party_num = party->get_member_num(items.actor2_ref);
|
|
scroll->display_string(party_num >= 0 ? party->get_actor_name(party_num)
|
|
: am->look_actor(items.actor2_ref));
|
|
scroll->display_string("\n");
|
|
|
|
if (party_num < 0) // can't force potions on non-party members
|
|
scroll->display_string("No effect\n");
|
|
else {
|
|
switch (obj->frame_n) {
|
|
case USE_U6_POTION_RED:
|
|
((U6Actor *)items.actor2_ref)->set_poisoned(false);
|
|
destroy_obj(obj);
|
|
break;
|
|
case USE_U6_POTION_YELLOW:
|
|
((U6Actor *)items.actor2_ref)->heal();
|
|
destroy_obj(obj);
|
|
break;
|
|
case USE_U6_POTION_GREEN:
|
|
((U6Actor *)items.actor2_ref)->set_poisoned(true);
|
|
destroy_obj(obj);
|
|
break;
|
|
case USE_U6_POTION_BLUE:
|
|
((U6Actor *)items.actor2_ref)->set_asleep(false);
|
|
destroy_obj(obj);
|
|
break;
|
|
case USE_U6_POTION_PURPLE:
|
|
((U6Actor *)items.actor2_ref)->set_protected(true);
|
|
destroy_obj(obj);
|
|
break;
|
|
case USE_U6_POTION_WHITE:
|
|
new U6WhitePotionEffect(2500, 6000, obj);
|
|
break; // wait for message to delete potion
|
|
case USE_U6_POTION_BLACK:
|
|
//new SpellTargetEffect(items.actor2_ref, obj);
|
|
// or effect_mgr->wait_for_effect(new SpellTargetEffect(items.actor2_ref), this, obj);
|
|
items.actor2_ref->set_invisible(true);
|
|
destroy_obj(obj);
|
|
break;
|
|
case USE_U6_POTION_ORANGE:
|
|
//items.actor2_ref->set_worktype(WORKTYPE_U6_SLEEP);
|
|
items.actor2_ref->set_asleep(true);
|
|
//party->set_active(party_num, !(items.actor2_ref->is_sleeping() || items.actor2_ref->is_paralyzed()));
|
|
player->set_actor(party->get_leader_actor());
|
|
player->set_mapwindow_centered(true);
|
|
destroy_obj(obj);
|
|
break;
|
|
|
|
default:
|
|
if (obj->frame_n <= 7) {
|
|
scroll->display_string("Drink %s potion!\n", u6_potions[obj->frame_n]);
|
|
//scroll->display_string(u6_potions[obj->frame_n]);
|
|
//scroll->display_string(" potion!\n");
|
|
} else
|
|
scroll->display_string("\nNo effect\n");
|
|
destroy_obj(obj);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
} else if (ev == USE_EVENT_INPUT_CANCEL) {
|
|
scroll->display_string("No effect\n");
|
|
return true;
|
|
} else if (ev == USE_EVENT_MESSAGE) { // assume message is from potion effect
|
|
if (*items.msg_ref == MESG_EFFECT_COMPLETE && obj->frame_n == USE_U6_POTION_WHITE) { // white
|
|
destroy_obj(obj);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool U6UseCode::lock_pick_dex_check() {
|
|
int dex = player->get_actor()->get_dexterity();
|
|
if (player->get_actor()->is_cursed()) {
|
|
if (dex <= 3)
|
|
dex = 1;
|
|
else
|
|
dex -= 3;
|
|
}
|
|
|
|
if ((int)NUVIE_RAND() % 30 < (45 - dex) / 2)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Use a key on obj_ref (a door). */
|
|
bool U6UseCode::use_key(Obj *obj, UseCodeEvent ev) {
|
|
Obj *door_obj = nullptr;
|
|
if (ev == USE_EVENT_USE) {
|
|
USECODE_SELECT_OBJ(door_obj, "On "); // door_obj <- items.obj_ref or from user
|
|
if (!door_obj) {
|
|
scroll->display_string("nothing\n");
|
|
return true;
|
|
} else {
|
|
if (UseCode::out_of_use_range(door_obj, false))
|
|
return true;
|
|
|
|
scroll->display_string(obj_manager->get_obj_name(door_obj));
|
|
scroll->display_string("\n");
|
|
|
|
if (!is_door(door_obj) && !is_chest(door_obj)) {
|
|
scroll->display_string("No effect\n");
|
|
return true;
|
|
}
|
|
|
|
if (obj->obj_n == OBJ_U6_LOCK_PICK && lock_pick_dex_check() == true) {
|
|
Game::get_game()->get_sound_manager()->playSfx(NUVIE_SFX_FAILURE);
|
|
scroll->display_string("\nKey broke.\n");
|
|
if (obj->qty > 1) {
|
|
obj->qty -= 1;
|
|
} else {
|
|
UseCode::search_container(obj, false); //need to remove rolling pin if there is one
|
|
obj_manager->unlink_from_engine(obj);
|
|
delete_obj(obj);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//FIXME need to handle locked chests.
|
|
if (((obj->obj_n == OBJ_U6_KEY && door_obj->quality != 0 && door_obj->quality == obj->quality)
|
|
|| (obj->obj_n == OBJ_U6_LOCK_PICK && door_obj->quality == 0))
|
|
&& (is_closed_door(door_obj) || is_closed_chest(door_obj))
|
|
&& !is_magically_locked(door_obj)) {
|
|
if (is_locked(door_obj)) {
|
|
unlock(door_obj);
|
|
scroll->display_string("\nunlocked!\n");
|
|
} else {
|
|
lock(door_obj);
|
|
scroll->display_string("\nlocked!\n");
|
|
}
|
|
} else if (is_door(door_obj) && door_obj->frame_n <= 3
|
|
&& ((obj->obj_n == OBJ_U6_KEY && door_obj->quality != 0 && door_obj->quality == obj->quality)
|
|
|| (obj->obj_n == OBJ_U6_LOCK_PICK && door_obj->quality == 0)))
|
|
scroll->display_string("\nCan't (Un)lock an opened door\n");
|
|
else
|
|
scroll->display_string("\nNo effect\n");
|
|
return true;
|
|
}
|
|
} else if (ev == USE_EVENT_INPUT_CANCEL) {
|
|
scroll->display_string("nothing\n");
|
|
return true;
|
|
} else if (ev == USE_EVENT_GET && obj->obj_n == OBJ_U6_LOCK_PICK) { //need to remove rolling pin if there is one
|
|
UseCode::search_container(obj, false);
|
|
return true;
|
|
} else if (ev == USE_EVENT_SEARCH && obj->obj_n == OBJ_U6_LOCK_PICK) //need to remove rolling pin if there is one
|
|
return UseCode::search_container(obj);
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/* USE: Enter and exit sea-going vessels. (entire party)
|
|
*/
|
|
bool U6UseCode::use_boat(Obj *obj, UseCodeEvent ev) {
|
|
Actor *ship_actor;
|
|
uint16 lx, ly;
|
|
uint8 lz;
|
|
|
|
if (ev == USE_EVENT_SEARCH)
|
|
return UseCode::search_container(obj);
|
|
else if (ev == USE_EVENT_USE && obj->has_container())
|
|
return use_container(obj, USE_EVENT_USE);
|
|
else if (ev == USE_EVENT_LOOK || ev == USE_EVENT_GET) {
|
|
if (obj->quality != 0 && party->has_obj(OBJ_U6_SHIP_DEED, obj->quality)) {
|
|
if (obj->obj_n == OBJ_U6_SKIFF)
|
|
obj->set_ok_to_take(true);
|
|
obj->quality = 0;
|
|
}
|
|
if (ev == USE_EVENT_GET)
|
|
return true;
|
|
}
|
|
|
|
if (ev != USE_EVENT_USE)
|
|
return false;
|
|
ship_actor = actor_manager->get_actor(0); //get the vehicle actor.
|
|
|
|
// get out of boat
|
|
if (party->is_in_vehicle()) {
|
|
ship_actor->get_location(&lx, &ly, &lz); //retrieve actor position for land check.
|
|
|
|
if (use_boat_find_land(&lx, &ly, &lz)) { //we must be next to land to disembark
|
|
Obj *objP = ship_actor->make_obj();
|
|
objP->qty = ship_actor->get_hp(); // Hull Strength
|
|
|
|
party->exit_vehicle(lx, ly, lz);
|
|
|
|
obj_manager->add_obj(objP);
|
|
} else {
|
|
scroll->display_string("\nOnly next to land.\n");
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if (obj->is_on_map() == false) {
|
|
scroll->display_string("\nNot usable\n");
|
|
return true;
|
|
}
|
|
if ((obj->obj_n == OBJ_U6_SKIFF || obj->obj_n == OBJ_U6_RAFT)
|
|
&& !map->is_water(obj->x, obj->y, obj->z, true)) {
|
|
scroll->display_string("\nYou must place it in water first.\n");
|
|
return true;
|
|
}
|
|
if (!player->in_party_mode()) {
|
|
scroll->display_string("\nNot in solo mode.\n");
|
|
return true;
|
|
}
|
|
|
|
if (obj->obj_n == OBJ_U6_SHIP) { //If we are using a ship we need to use its center object.
|
|
obj = use_boat_find_center(obj); //return the center object
|
|
if (obj == nullptr) {
|
|
scroll->display_string("\nShip not usable\n");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (obj->quality != 0) { //deed check
|
|
if (party->has_obj(OBJ_U6_SHIP_DEED, obj->quality) == false) {
|
|
scroll->display_string("\nA deed is required.\n");
|
|
return true;
|
|
}
|
|
if (obj->obj_n == OBJ_U6_SKIFF)
|
|
obj->set_ok_to_take(true);
|
|
obj->quality = 0;
|
|
}
|
|
|
|
if (UseCode::out_of_use_range(obj, true))
|
|
return true;
|
|
|
|
// walk to vehicle if necessary
|
|
if (!party->is_at(obj->x, obj->y, obj->z)) {
|
|
party->enter_vehicle(obj);
|
|
return true;
|
|
}
|
|
|
|
// use it (replace ship with vehicle actor)
|
|
ship_actor->init_from_obj(obj, ACTOR_CHANGE_BASE_OBJ_N);
|
|
if (obj->obj_n == OBJ_U6_SHIP)
|
|
ship_actor->set_hp(obj->qty); // Hull Strength
|
|
ship_actor->show(); // Swift!
|
|
obj_manager->remove_obj_from_map(obj);
|
|
delete_obj(obj);
|
|
|
|
party->hide(); // set in-vehicle
|
|
player->set_actor(ship_actor);
|
|
party->set_in_vehicle(true);
|
|
return true;
|
|
}
|
|
|
|
inline Obj *U6UseCode::use_boat_find_center(Obj *obj) {
|
|
Obj *new_obj;
|
|
uint16 x, y;
|
|
uint8 ship_direction = (obj->frame_n % 8) / 2; //find the direction based on the frame_n
|
|
|
|
if (obj->frame_n >= 8 && obj->frame_n < 16) //center obj
|
|
return obj;
|
|
|
|
x = obj->x;
|
|
y = obj->y;
|
|
|
|
if (obj->frame_n < 8) { //front obj
|
|
switch (ship_direction) {
|
|
case NUVIE_DIR_N :
|
|
y++;
|
|
break;
|
|
case NUVIE_DIR_E :
|
|
x--;
|
|
break;
|
|
case NUVIE_DIR_S :
|
|
y--;
|
|
break;
|
|
case NUVIE_DIR_W :
|
|
x++;
|
|
break;
|
|
}
|
|
} else {
|
|
if (obj->frame_n >= 16 && obj->frame_n < 24) { //back obj
|
|
switch (ship_direction) {
|
|
case NUVIE_DIR_N :
|
|
y--;
|
|
break;
|
|
case NUVIE_DIR_E :
|
|
x++;
|
|
break;
|
|
case NUVIE_DIR_S :
|
|
y++;
|
|
break;
|
|
case NUVIE_DIR_W :
|
|
x--;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
new_obj = obj_manager->get_objBasedAt(x, y, obj->z, true);
|
|
|
|
if (new_obj != nullptr && new_obj->obj_n == OBJ_U6_SHIP)
|
|
return new_obj;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
inline bool U6UseCode::use_boat_find_land(uint16 *x, uint16 *y, uint8 *z) {
|
|
if (map->is_passable(*x, *y - 1, *z)) { //UP
|
|
*y = *y - 1;
|
|
return true;
|
|
}
|
|
if (map->is_passable(*x + 1, *y, *z)) { //RIGHT
|
|
*x = *x + 1;
|
|
return true;
|
|
}
|
|
if (map->is_passable(*x, *y + 1, *z)) { //DOWN
|
|
*y = *y + 1;
|
|
return true;
|
|
}
|
|
if (map->is_passable(*x - 1, *y, *z)) { //LEFT
|
|
*x = *x - 1;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* construct a balloon using the balloon plans */
|
|
bool U6UseCode::use_balloon_plans(Obj *obj, UseCodeEvent ev) {
|
|
if (ev == USE_EVENT_LOOK)
|
|
return look_sign(obj, ev);
|
|
|
|
MapCoord player_location = player->get_actor()->get_location();
|
|
bool missing_obj = false;
|
|
Obj *balloon;
|
|
|
|
if (ev != USE_EVENT_USE)
|
|
return false;
|
|
|
|
scroll->display_string("\n");
|
|
|
|
//make sure the party is carrying the required parts.
|
|
|
|
if (!party->has_obj(OBJ_U6_MAMMOTH_SILK_BAG, 0)) {
|
|
scroll->display_string("Missing a mammoth silk bag.\n");
|
|
missing_obj = true;
|
|
}
|
|
|
|
if (!party->has_obj(OBJ_U6_BALLOON_BASKET, 0)) {
|
|
scroll->display_string("Missing a balloon basket.\n");
|
|
missing_obj = true;
|
|
}
|
|
|
|
if (!party->has_obj(OBJ_U6_CAULDRON, 0)) {
|
|
scroll->display_string("Missing a cauldron.\n");
|
|
missing_obj = true;
|
|
}
|
|
|
|
if (!party->has_obj(OBJ_U6_ROPE, 0)) {
|
|
scroll->display_string("Missing a rope.\n");
|
|
missing_obj = true;
|
|
}
|
|
|
|
// Make the balloon if we have all the parts.
|
|
if (!missing_obj) {
|
|
party->remove_obj(OBJ_U6_MAMMOTH_SILK_BAG, 0);
|
|
party->remove_obj(OBJ_U6_BALLOON_BASKET, 0);
|
|
party->remove_obj(OBJ_U6_CAULDRON, 0);
|
|
party->remove_obj(OBJ_U6_ROPE, 0);
|
|
|
|
balloon = new_obj(OBJ_U6_BALLOON, 0, player_location.x, player_location.y, player_location.z);
|
|
|
|
if (balloon && obj_manager->add_obj(balloon)) {
|
|
balloon->set_ok_to_take(true);
|
|
scroll->display_string("Done!\n");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/* USE: balloon. (entire party)
|
|
*/
|
|
bool U6UseCode::use_balloon(Obj *obj, UseCodeEvent ev) {
|
|
Actor *balloon_actor;
|
|
Actor *balloonist;
|
|
MapCoord spot(0, 0, 0);
|
|
uint16 lx, ly;
|
|
uint8 lz;
|
|
|
|
if (ev != USE_EVENT_USE)
|
|
return false;
|
|
|
|
if (Game::get_game()->get_player()->in_party_mode()) {
|
|
balloonist = Game::get_game()->get_party()->get_leader_actor();
|
|
} else {
|
|
balloonist = Game::get_game()->get_player()->get_actor();
|
|
}
|
|
spot = balloonist->get_location();
|
|
if ((spot.z > 0) && (spot.z < 5)) {
|
|
scroll->display_string("\nNot usable\n");
|
|
return true;
|
|
}
|
|
|
|
if (obj->obj_n == OBJ_U6_BALLOON) {
|
|
if (!obj->is_on_map()) {
|
|
// if in party mode, find a spot around the avatar that is_passable,
|
|
// else a spot around the person using it.
|
|
// drop the balloon there, and inflate it.
|
|
|
|
uint16 x, y;
|
|
x = spot.x;
|
|
y = spot.y;
|
|
|
|
bool dropped = false;
|
|
for (sint8 iy = -1; iy < 2; iy++) { // FIXME scan order
|
|
for (sint8 ix = -1; ix < 2; ix++) {
|
|
DEBUG(0, LEVEL_DEBUGGING, "can drop at %d %d?\n", ix, iy);
|
|
if (Game::get_game()->get_map_window()->can_drop_or_move_obj(x + ix, y + iy, balloonist, obj) == MSG_SUCCESS) {
|
|
DEBUG(0, LEVEL_DEBUGGING, "yes, can drop at %d %d.\n", x + ix, y + iy);
|
|
obj_manager->unlink_from_engine(obj);
|
|
obj->x = x + ix;
|
|
obj->y = y + iy;
|
|
obj->z = spot.z;
|
|
|
|
dropped = true;
|
|
iy = 2;
|
|
ix = 2;
|
|
}
|
|
}
|
|
}
|
|
if (!dropped) {
|
|
// drop on 'spot' instead.
|
|
obj_manager->unlink_from_engine(obj);
|
|
obj->x = spot.x;
|
|
obj->y = spot.y;
|
|
obj->z = spot.z;
|
|
dropped = true;
|
|
}
|
|
obj->status |= OBJ_STATUS_OK_TO_TAKE;
|
|
obj_manager->add_obj(obj, OBJ_ADD_TOP);
|
|
}
|
|
|
|
obj->obj_n = OBJ_U6_INFLATED_BALLOON;
|
|
obj->frame_n = 3;
|
|
scroll->display_string("\nDone!\n");
|
|
return true;
|
|
}
|
|
|
|
balloon_actor = actor_manager->get_actor(0); //get the vehicle actor.
|
|
|
|
// get out of balloon
|
|
if (party->is_in_vehicle()) { // FIXME: use balloon when in skiff...
|
|
balloon_actor->get_location(&lx, &ly, &lz); //retrieve actor position for land check.
|
|
|
|
if (use_boat_find_land(&lx, &ly, &lz)) { //we must be next to land to disembark
|
|
Obj *objP;
|
|
|
|
party->show();
|
|
balloon_actor->hide();
|
|
balloon_actor->set_worktype(0);
|
|
|
|
player->set_actor(party->get_actor(0));
|
|
player->move(lx, ly, lz, false);
|
|
balloon_actor->obj_n = OBJ_U6_NO_VEHICLE;
|
|
balloon_actor->frame_n = 0;
|
|
balloon_actor->init();
|
|
balloon_actor->move(0, 0, 0, ACTOR_FORCE_MOVE);
|
|
|
|
objP = new_obj(OBJ_U6_BALLOON, 0, lx, ly, lz);
|
|
objP->status |= OBJ_STATUS_OK_TO_TAKE;
|
|
obj_manager->add_obj(objP, OBJ_ADD_TOP);
|
|
} else {
|
|
scroll->display_string("\nOnly next to land.\n");
|
|
return true;
|
|
}
|
|
|
|
party->set_in_vehicle(false);
|
|
|
|
return true;
|
|
}
|
|
|
|
if (!player->in_party_mode()) {
|
|
scroll->display_string("\nNot in solo mode.\n");
|
|
return true;
|
|
}
|
|
|
|
if (UseCode::out_of_use_range(obj, true))
|
|
return true;
|
|
// walk to vehicle if necessary
|
|
if (!party->is_at(obj->x, obj->y, obj->z)) {
|
|
party->enter_vehicle(obj);
|
|
return true; // display prompt
|
|
}
|
|
|
|
// use it (replace ship with vehicle actor)
|
|
balloon_actor->init_from_obj(obj, ACTOR_CHANGE_BASE_OBJ_N);
|
|
balloon_actor->show(); // Swift!
|
|
obj_manager->remove_obj_from_map(obj);
|
|
delete_obj(obj);
|
|
|
|
party->hide(); // set in-vehicle
|
|
player->set_actor(balloon_actor);
|
|
party->set_in_vehicle(true);
|
|
return true;
|
|
}
|
|
|
|
|
|
/* using a cow fills an empty bucket in the player's inventory with milk */
|
|
bool U6UseCode::use_cow(Obj *obj, UseCodeEvent ev) {
|
|
if (ev != USE_EVENT_USE)
|
|
return false;
|
|
|
|
// return fill_bucket(OBJ_U6_BUCKET_OF_MILK);
|
|
fill_bucket(OBJ_U6_BUCKET_OF_MILK);
|
|
return true;
|
|
}
|
|
|
|
/* using a well fills an empty bucket in the player's inventory with water */
|
|
bool U6UseCode::use_well(Obj *obj, UseCodeEvent ev) {
|
|
if (ev != USE_EVENT_USE)
|
|
return false;
|
|
|
|
// return fill_bucket(OBJ_U6_BUCKET_OF_WATER);
|
|
fill_bucket(OBJ_U6_BUCKET_OF_WATER);
|
|
return true;
|
|
}
|
|
|
|
// fill an empty bucket in the player actor's inventory with some liquid
|
|
bool U6UseCode::fill_bucket(uint16 filled_bucket_obj_n) {
|
|
Actor *player_actor;
|
|
Obj *bucket;
|
|
|
|
player_actor = player->get_actor();
|
|
|
|
if (!player_actor->inventory_has_object(OBJ_U6_BUCKET)) {
|
|
if (player_actor->inventory_has_object(OBJ_U6_BUCKET_OF_MILK) ||
|
|
player_actor->inventory_has_object(OBJ_U6_BUCKET_OF_WATER)) {
|
|
scroll->display_string("\nYou need an empty bucket.\n");
|
|
return true;
|
|
} else {
|
|
scroll->display_string("\nYou need a bucket.\n");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Fill the first empty bucket in player's inventory.
|
|
|
|
bucket = player_actor->inventory_get_object(OBJ_U6_BUCKET);
|
|
player_actor->inventory_remove_obj(bucket);
|
|
|
|
bucket->obj_n = filled_bucket_obj_n;
|
|
|
|
player_actor->inventory_add_object(bucket);
|
|
|
|
scroll->display_string("\nDone\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// USE: replace a bucket of milk in the player's inventory with butter
|
|
bool U6UseCode::use_churn(Obj *obj, UseCodeEvent ev) {
|
|
ViewManager *view_manager = game->get_view_manager();
|
|
Actor *player_actor;
|
|
Obj *bucket;
|
|
Obj *butter;
|
|
|
|
player_actor = player->get_actor();
|
|
|
|
if (!player_actor->inventory_has_object(OBJ_U6_BUCKET_OF_MILK)) {
|
|
scroll->display_string("\nYou need some milk.\n");
|
|
return true;
|
|
}
|
|
|
|
bucket = player_actor->inventory_get_object(OBJ_U6_BUCKET_OF_MILK);
|
|
player_actor->inventory_remove_obj(bucket);
|
|
|
|
bucket->obj_n = OBJ_U6_BUCKET;
|
|
|
|
butter = new Obj();
|
|
|
|
butter->obj_n = OBJ_U6_BUTTER;
|
|
player_actor->inventory_add_object(butter);
|
|
player_actor->inventory_add_object(bucket);
|
|
if (!game->is_new_style())
|
|
view_manager->set_inventory_mode();
|
|
|
|
view_manager->update(); //FIX this should be moved higher up in UseCode
|
|
|
|
scroll->display_string("\nDone\n");
|
|
return true;
|
|
}
|
|
|
|
|
|
// USE: fill an empty honey jar in the player's inventory
|
|
bool U6UseCode::use_beehive(Obj *obj, UseCodeEvent ev) {
|
|
ViewManager *view_manager = game->get_view_manager();
|
|
Actor *player_actor;
|
|
Obj *honey_jar;
|
|
|
|
player_actor = player->get_actor();
|
|
|
|
if (!player_actor->inventory_has_object(OBJ_U6_HONEY_JAR)) {
|
|
if (player_actor->inventory_has_object(OBJ_U6_JAR_OF_HONEY)) {
|
|
scroll->display_string("\nYou need an empty honey jar.\n");
|
|
} else {
|
|
scroll->display_string("\nYou need a honey jar.\n");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
honey_jar = player_actor->inventory_get_object(OBJ_U6_HONEY_JAR);
|
|
player_actor->inventory_remove_obj(honey_jar);
|
|
|
|
honey_jar->obj_n = OBJ_U6_JAR_OF_HONEY; //fill the empty jar with honey.
|
|
|
|
player_actor->inventory_add_object(honey_jar); // add the filled jar back to the player's inventory
|
|
if (!game->is_new_style())
|
|
view_manager->set_inventory_mode();
|
|
|
|
view_manager->update(); //FIX this should be moved higher up in UseCode
|
|
|
|
scroll->display_string("\nDone\n");
|
|
return true;
|
|
}
|
|
|
|
|
|
/* USE: Mount or dismount from a horse. Don't allow using another horse if
|
|
* already riding one.
|
|
*/
|
|
bool U6UseCode::use_horse(Obj *obj, UseCodeEvent ev) {
|
|
Actor *actor, *player_actor;
|
|
Obj *actor_obj;
|
|
|
|
if (ev != USE_EVENT_USE)
|
|
return false;
|
|
|
|
actor = actor_manager->get_actor(obj->quality); // horse or horse with rider
|
|
if (!actor)
|
|
return false;
|
|
|
|
player_actor = items.actor_ref;
|
|
if (player_actor->get_actor_num() == U6_SHERRY_ACTOR_NUM) {
|
|
scroll->display_string("Sherry says: \"Eeek!!! I'm afraid of horses!\"\n");
|
|
return true;
|
|
} else if (player_actor->get_actor_num() == U6_BEHLEM_ACTOR_NUM) {
|
|
scroll->display_string("BehLem says: \"Horses are for food!\"\n");
|
|
return true;
|
|
} else if (obj->obj_n == OBJ_U6_HORSE && player_actor->obj_n == OBJ_U6_HORSE_WITH_RIDER) {
|
|
scroll->display_string("You're already on a horse!\n");
|
|
return true;
|
|
} else if (party->is_in_vehicle()) {
|
|
Game::get_game()->get_event()->display_not_aboard_vehicle(false);
|
|
return true;
|
|
}
|
|
|
|
actor_obj = actor->make_obj();
|
|
|
|
//dismount from horse. revert to original actor type.
|
|
//Add a temporary horse actor onto the map.
|
|
if (obj->obj_n == OBJ_U6_HORSE_WITH_RIDER) {
|
|
actor->clear();
|
|
if (actor == player_actor)
|
|
actor->set_worktype(0x02); // PLAYER
|
|
|
|
actor_obj->obj_n = actor->base_obj_n; //revert to normal actor type
|
|
actor_obj->frame_n = actor->old_frame_n;
|
|
|
|
actor->init_from_obj(actor_obj);
|
|
|
|
// create a temporary horse on the map.
|
|
actor_manager->create_temp_actor(OBJ_U6_HORSE, NO_OBJ_STATUS, obj->x, obj->y, obj->z, ACTOR_ALIGNMENT_DEFAULT, WORKTYPE_U6_ANIMAL_WANDER);
|
|
} else if (!actor_manager->is_temp_actor(actor)) { // Try to mount horse. Don't use permanent Actors eg Smith, push-me pull-you
|
|
scroll->display_string("\nHorse not boardable!\n");
|
|
} else { // mount up.
|
|
if (UseCode::out_of_use_range(obj, true))
|
|
return true;
|
|
actor_manager->clear_actor(actor); //clear the temp horse actor from the map.
|
|
|
|
actor_obj->obj_n = OBJ_U6_HORSE_WITH_RIDER;
|
|
|
|
player_actor->move(actor_obj->x, actor_obj->y, actor_obj->z); //this will center the map window
|
|
player_actor->init_from_obj(actor_obj);
|
|
|
|
delete_obj(actor_obj);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool U6UseCode::use_fan(Obj *obj, UseCodeEvent ev) {
|
|
// Directions rotated clockwise by 45 deg.
|
|
NuvieDir next_wind_dir_tbl[] = {
|
|
NUVIE_DIR_NE, NUVIE_DIR_SE, NUVIE_DIR_SW, NUVIE_DIR_NW,
|
|
NUVIE_DIR_E, NUVIE_DIR_S, NUVIE_DIR_W, NUVIE_DIR_N};
|
|
Weather *weather = game->get_weather();
|
|
scroll->display_string("\nYou feel a breeze.\n");
|
|
|
|
NuvieDir wind_dir = weather->get_wind_dir();
|
|
|
|
if (wind_dir == NUVIE_DIR_NONE)
|
|
wind_dir = NUVIE_DIR_NW;
|
|
|
|
//cycle through the wind directions.
|
|
weather->set_wind_dir(next_wind_dir_tbl[wind_dir]);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* USE: Sextant. Display Latitude/Longitude coords centered on LB's castle.
|
|
*/
|
|
bool U6UseCode::use_sextant(Obj *obj, UseCodeEvent ev) {
|
|
MapCoord location;
|
|
char buf[18]; // "\nxxoS, xxoE\n"
|
|
char lat, lon;
|
|
uint16 x, y;
|
|
|
|
if (ev != USE_EVENT_USE)
|
|
return false;
|
|
|
|
location = player->get_actor()->get_location();
|
|
|
|
//only use sextant on surface level or in the gargoyle underworld.
|
|
if (location.z == 0 || location.z == 5) {
|
|
x = location.x / (location.z ? 2 : 8);
|
|
if (x > 38) {
|
|
lon = 'E';
|
|
x -= 38;
|
|
} else {
|
|
x = 38 - x;
|
|
lon = 'W';
|
|
}
|
|
|
|
y = location.y / (location.z ? 2 : 8);
|
|
if (y > 45) {
|
|
lat = 'S';
|
|
y -= 45;
|
|
} else {
|
|
y = 45 - y;
|
|
lat = 'N';
|
|
}
|
|
|
|
Common::sprintf_s(buf, "\n%d{%c, %d{%c\n", y, lat, x, lon);
|
|
scroll->display_string(buf);
|
|
} else
|
|
scroll->display_string("\nNot usable\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool U6UseCode::use_staff(Obj *obj, UseCodeEvent ev) {
|
|
if (ev != USE_EVENT_USE)
|
|
return false;
|
|
|
|
if (obj->is_readied() == false) {
|
|
scroll->display_string("\nNot readied.\n");
|
|
return true;
|
|
}
|
|
|
|
Obj *charge = obj->find_in_container(OBJ_U6_CHARGE, 0, OBJ_NOMATCH_QUALITY);
|
|
|
|
if (charge) {
|
|
uint8 spell_num = charge->quality;
|
|
obj_manager->unlink_from_engine(charge);
|
|
delete_obj(charge);
|
|
|
|
Game::get_game()->get_event()->cast_spell_directly(spell_num);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Pass: Allow normal move if player's Quest Flag is set.
|
|
*/
|
|
bool U6UseCode::pass_quest_barrier(Obj *obj, UseCodeEvent ev) {
|
|
if (ev == USE_EVENT_PASS)
|
|
if (player->get_quest_flag() == 0) {
|
|
// block everyone, only print message when player attempts to pass
|
|
if (items.actor_ref == player->get_actor())
|
|
scroll->message("\n\"Thou art not upon a Sacred Quest!\n"
|
|
"Passage denied!\"\n\n");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/* LOOK: Get (possibly translate) book data for readable object. Disallow search
|
|
* (return true) if book data was displayed.
|
|
*/
|
|
bool U6UseCode::look_sign(Obj *obj, UseCodeEvent ev) {
|
|
char *data;
|
|
Book *book = game->get_book(); // ??
|
|
|
|
if (ev == USE_EVENT_LOOK) {
|
|
MapCoord obj_loc = MapCoord(obj->x, obj->y, obj->z);
|
|
MapCoord player_loc = player->get_actor()->get_location();
|
|
InterfaceType interface = game->get_map_window()->get_interface();
|
|
bool too_far = (player_loc.distance(obj_loc) > 1 && interface == INTERFACE_NORMAL);
|
|
bool blocked = (interface != INTERFACE_IGNORE_BLOCK
|
|
&& !game->get_map_window()->can_get_obj(player->get_actor(), obj));
|
|
if ((obj->quality == 0 && obj->obj_n != OBJ_U6_BOOK) || (!obj->is_in_inventory()
|
|
&& (obj->obj_n == OBJ_U6_BOOK || obj->obj_n == OBJ_U6_SCROLL) && (too_far || blocked))) {
|
|
scroll->display_string("\n");
|
|
return true; // display prompt
|
|
}
|
|
// read
|
|
if (items.actor_ref == player->get_actor()) {
|
|
scroll->display_string(":\n\n");
|
|
uint8 book_num = obj->quality - 1;
|
|
if (obj->quality == 0)
|
|
book_num = 126;
|
|
if ((data = book->get_book_data(book_num))) {
|
|
/*
|
|
// FIX Any alternate-font text is in < >, Runic is capitalized,
|
|
// Gargish is lower-case. Translations follow untranslated text and
|
|
// are wrapped in & &.
|
|
if(data[0] == '<' && data[strlen(data)-1] == '>') //Britannian text is wrapped in '<' '>' chars
|
|
{
|
|
scroll->display_string(&data[1],strlen(data)-2, 1); // 1 for britannian font.
|
|
scroll->display_string("\n",1);
|
|
}
|
|
else
|
|
{
|
|
*/
|
|
bool using_gump = game->is_using_text_gumps();
|
|
if (using_gump) {
|
|
switch (obj->obj_n) {
|
|
case OBJ_U6_BOOK:
|
|
case OBJ_U6_PICTURE:
|
|
case OBJ_U6_SCROLL:
|
|
case OBJ_U6_GRAVE:
|
|
case OBJ_U6_CROSS: // wooden cross used as grave marker (text like grave)
|
|
case OBJ_U6_BALLOON_PLANS:
|
|
case OBJ_U6_BOOK_OF_CIRCLES:
|
|
case OBJ_U6_CODEX:
|
|
game->get_view_manager()->open_scroll_gump(data, strlen(data));
|
|
break;
|
|
case OBJ_U6_SIGN:
|
|
if (strlen(data) > 20) { // FIXME sign text needs to fit on multiple lines
|
|
using_gump = false;
|
|
break;
|
|
}
|
|
game->get_view_manager()->open_sign_gump(data, strlen(data));
|
|
break;
|
|
case OBJ_U6_SIGN_ARROW:
|
|
|
|
default:
|
|
using_gump = false;
|
|
}
|
|
}
|
|
if (!using_gump) {
|
|
scroll->set_autobreak(true);
|
|
scroll->display_string(data, strlen(data)); //normal font
|
|
scroll->display_string("\n\t"); // '\t' = auto break off.
|
|
}
|
|
//scroll->set_autobreak(false);
|
|
// }
|
|
free(data);
|
|
}
|
|
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/* LOOK: Display the current time. Disallow search.
|
|
*/
|
|
bool U6UseCode::look_clock(Obj *obj, UseCodeEvent ev) {
|
|
GameClock *clock = game->get_clock();
|
|
if (obj->obj_n == OBJ_U6_SUNDIAL
|
|
&& (clock->get_hour() < 5 || clock->get_hour() > 19))
|
|
return true; // don't get time from sundial at night
|
|
if (ev == USE_EVENT_LOOK && items.actor_ref == player->get_actor()) {
|
|
scroll->display_string("\nThe time is ");
|
|
scroll->display_string(clock->get_time_string());
|
|
scroll->display_string("\n");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/* test (need to determine use of true/false return)
|
|
*/
|
|
bool U6UseCode::look_mirror(Obj *obj, UseCodeEvent ev) {
|
|
// ViewManager *view_manager = game->get_view_manager();
|
|
if (ev == USE_EVENT_LOOK && items.actor_ref == player->get_actor()) {
|
|
uint16 x, y;
|
|
uint8 z;
|
|
items.actor_ref->get_location(&x, &y, &z);
|
|
if (x == obj->x && y > obj->y && y <= (obj->y + 2)) {
|
|
scroll->display_string("\nYou can see yourself!");
|
|
game->get_event()->display_portrait(items.actor_ref);
|
|
}
|
|
scroll->display_string("\n");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/* PASS: if not in party mode, say that you cannot enter and do normal move
|
|
* else walk all party members to cave, give dungeon name, and move to dungeon
|
|
*/
|
|
bool U6UseCode::enter_dungeon(Obj *obj, UseCodeEvent ev) {
|
|
if (!party->contains_actor(items.actor_ref))
|
|
return false;
|
|
|
|
const char *prefix = "", *dungeon_name = "";
|
|
uint16 x = obj->x, y = obj->y;
|
|
uint8 z = obj->z;
|
|
|
|
if (party->is_in_vehicle()) //don't enter if in a balloon.
|
|
return true;
|
|
|
|
if (!player->in_party_mode()) {
|
|
scroll->display_string("\n\nNot in solo mode.\n");
|
|
return true;
|
|
}
|
|
|
|
if (ev == USE_EVENT_USE && UseCode::out_of_use_range(obj, true))
|
|
return true;
|
|
|
|
if (obj->quality < 21)
|
|
dungeon_name = u6_dungeons[obj->quality];
|
|
if (obj->quality >= 1 && obj->quality <= 7)
|
|
prefix = "dungeon ";
|
|
else if (obj->quality >= 9 && obj->quality <= 11)
|
|
prefix = "shrine of ";
|
|
else
|
|
prefix = "";
|
|
|
|
party->dismount_from_horses();
|
|
|
|
// don't activate if autowalking from linking exit
|
|
if ((ev == USE_EVENT_PASS || ev == USE_EVENT_USE) && items.actor_ref == player->get_actor() && !party->get_autowalk()) {
|
|
ActorManager *actorMan = Game::get_game()->get_actor_manager();
|
|
if (obj->quality != 0 && party->contains_actor(3) && actorMan->get_actor(3)->is_alive()) {
|
|
// scroll->printf("%s says, \"This is the %s%s.\"\n\n",blah->name, prefix, dungeon_name);
|
|
scroll->display_string("Shamino says, \"This is the ");
|
|
scroll->display_string(prefix);
|
|
scroll->display_string(dungeon_name);
|
|
scroll->display_string(".\"\n\n");
|
|
scroll->display_prompt();
|
|
}
|
|
MapCoord entrance(x, y, z);
|
|
// going down
|
|
if (z == 0) { // from surface, do superchunk translation
|
|
x = (x & 0x07) | (x >> 2 & 0xF8);
|
|
y = (y & 0x07) | (y >> 2 & 0xF8);
|
|
}
|
|
if (z < 5)
|
|
z += 1;
|
|
else
|
|
z -= 1;
|
|
|
|
MapCoord exitPos(x, y, z);
|
|
// if(obj->obj_n == OBJ_U6_HOLE) // fall down hole faster
|
|
// party->walk(&entrance, &exitPos, 100);
|
|
// else
|
|
// party->walk(&entrance, &exitPos);
|
|
party->walk(&entrance, &exitPos, 100);
|
|
game->get_weather()->set_wind_dir(NUVIE_DIR_NONE);
|
|
return true;
|
|
} else if ((ev == USE_EVENT_PASS || ev == USE_EVENT_USE) && party->get_autowalk()) // party can use now
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool U6UseCode::enter_moongate(Obj *obj, UseCodeEvent ev) {
|
|
/* shared between blue and red gates */
|
|
/* PASS: if not in party mode, say that you cannot enter and do normal move
|
|
* else walk all party members to moongate and teleport.
|
|
*/
|
|
uint16 x = obj->x, y = obj->y;
|
|
uint8 z = obj->z;
|
|
MapCoord exitPos(0, 0, 0);
|
|
|
|
if (party->is_in_vehicle())
|
|
return true;
|
|
if (items.mapcoord_ref->x != x)
|
|
return true; // don't step onto the left tile of a moongate
|
|
|
|
if (!player->in_party_mode()) {
|
|
scroll->display_string("\nYou must be in party mode to enter.\n\n");
|
|
scroll->display_prompt();
|
|
return true;
|
|
}
|
|
|
|
// don't activate if autowalking from linking exitPos
|
|
if (ev == USE_EVENT_PASS && items.actor_ref == player->get_actor() && !party->get_autowalk()) {
|
|
if (obj->obj_n == OBJ_U6_RED_GATE) {
|
|
if (obj->quality > 25) {
|
|
DEBUG(0, LEVEL_ERROR, "invalid moongate destination %d\n", obj->quality);
|
|
return false;
|
|
}
|
|
if (!party->has_obj(87, 0, false)) { // make sure orb of moons is in party inventory
|
|
scroll->display_string("\nYou forgot the Orb of the Moons!\n");
|
|
return true;
|
|
}
|
|
if ((obj->quality > 0 && obj->quality < 12) ||
|
|
(obj->quality > 14 && obj->quality < 26)) { //positions 0, 12, 13 and 14 go nowhere.
|
|
x = red_moongate_tbl[obj->quality].x; // set our moongate destination from the lookup table.
|
|
y = red_moongate_tbl[obj->quality].y;
|
|
z = red_moongate_tbl[obj->quality].z;
|
|
}
|
|
exitPos = MapCoord(x, y, z);
|
|
} else if (obj->obj_n == OBJ_U6_MOONGATE) {
|
|
// FIXME: Duplication from PartyView, this ought to be separated
|
|
/* we don't care if the moons are in the sky,
|
|
* (to make permanent moongates work)
|
|
* if the moongate is there, it goes somewhere
|
|
*/
|
|
Weather *weather = game->get_weather();
|
|
GameClock *clock = Game::get_game()->get_clock();
|
|
uint8 day = clock->get_day();
|
|
uint8 hour = clock->get_hour();
|
|
uint8 phaseTrammel = uint8(nearbyint((day - 1) / TRAMMEL_PHASE)) % 8;
|
|
sint8 phaseb = (day - 1) % uint8(nearbyint(FELUCCA_PHASE * 8)) - 1;
|
|
uint8 phaseFelucca = (phaseb >= 0) ? phaseb : 0;
|
|
uint8 posTrammel = ((hour + 1) + 3 * phaseTrammel) % 24;
|
|
uint8 posFelucca = ((hour - 1) + 3 * phaseFelucca) % 24;
|
|
uint8 absTrammel = abs(posTrammel - 12);
|
|
uint8 absFelucca = abs(posFelucca - 12);
|
|
if (absTrammel < absFelucca) {
|
|
// Trammel wins.
|
|
exitPos = weather->get_moonstone(8 - phaseTrammel);
|
|
} else {
|
|
// Feluccality!
|
|
exitPos = weather->get_moonstone(8 - phaseFelucca);
|
|
}
|
|
if (exitPos.x == 0 && exitPos.y == 0 && exitPos.z == 0) {
|
|
exitPos = MapCoord(x, y, z); // stay put.
|
|
}
|
|
}
|
|
party->walk(obj, &exitPos);
|
|
return true;
|
|
} else if (ev == USE_EVENT_PASS && party->get_autowalk()) // party can use now
|
|
if (party->contains_actor(items.actor_ref))
|
|
return true;
|
|
return true;
|
|
}
|
|
|
|
|
|
/* USE: Light powder keg if unlit
|
|
* MESSAGE: Timed: Explode; Effect complete: delete powder keg
|
|
*/
|
|
bool U6UseCode::use_powder_keg(Obj *obj, UseCodeEvent ev) {
|
|
if (ev == USE_EVENT_USE) {
|
|
game->get_script()->call_use_keg(obj);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/* Use: Fire! (block input, start cannonball anim, release input on hit)
|
|
* Message: Effect complete. Return to prompt.
|
|
* Move: Change direction if necessary
|
|
*/
|
|
bool U6UseCode::use_cannon(Obj *obj, UseCodeEvent ev) {
|
|
MapCoord *mapcoord_ref = items.mapcoord_ref;
|
|
|
|
if (ev == USE_EVENT_USE) {
|
|
scroll->display_string("\nFire!\n");
|
|
// FIXME: new UseCodeEffect(obj, cannonballtile, dir) // sets WAIT mode
|
|
new CannonballEffect(obj); // sets WAIT mode
|
|
// Note: waits for effect to complete and sends MESG_EFFECT_COMPLETE
|
|
return false;
|
|
} else if (ev == USE_EVENT_MESSAGE) {
|
|
if (*items.msg_ref == MESG_EFFECT_COMPLETE) {
|
|
scroll->display_string("\n");
|
|
scroll->display_prompt();
|
|
}
|
|
return true;
|
|
} else if (ev == USE_EVENT_MOVE) {
|
|
// allow normal move
|
|
if ((obj->frame_n == 0 && mapcoord_ref->sy < 0)
|
|
|| (obj->frame_n == 1 && mapcoord_ref->sx > 0)
|
|
|| (obj->frame_n == 2 && mapcoord_ref->sy > 0)
|
|
|| (obj->frame_n == 3 && mapcoord_ref->sx < 0))
|
|
return true;
|
|
else { // aim cannon in new direction
|
|
if (mapcoord_ref->sy < 0)
|
|
obj->frame_n = 0;
|
|
else if (mapcoord_ref->sy > 0)
|
|
obj->frame_n = 2;
|
|
else if (mapcoord_ref->sx < 0)
|
|
obj->frame_n = 3;
|
|
else if (mapcoord_ref->sx > 0)
|
|
obj->frame_n = 1;
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/* USE: Hatch egg.
|
|
*/
|
|
bool U6UseCode::use_egg(Obj *obj, UseCodeEvent ev) {
|
|
EggManager *egg_manager = obj_manager->get_egg_manager();
|
|
bool success = egg_manager->spawn_egg(obj, NUVIE_RAND() % 100);
|
|
if (items.actor_ref)
|
|
scroll->display_string(success ? "\nSpawned!\n" : "\nNo effect.\n");
|
|
return true;
|
|
}
|
|
|
|
/* USE: Open spellbook for casting, if equipped.
|
|
* LOOK: Open for spell inspection.
|
|
*/
|
|
bool U6UseCode::use_spellbook(Obj *obj, UseCodeEvent ev) {
|
|
if (ev == USE_EVENT_USE) {
|
|
Game::get_game()->get_event()->endAction(); // FIXME: this should call Magic directly
|
|
Game::get_game()->get_event()->newAction(CAST_MODE);
|
|
if (obj->is_readied()) {
|
|
/* TODO open spellbook for casting */
|
|
|
|
}
|
|
} else if (ev == USE_EVENT_LOOK) {
|
|
scroll->display_string("\n");
|
|
/* TODO open spellbook for reading */
|
|
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Use: Light torch if readied or on the ground.
|
|
* Ready: Get a torch from a stack and equip it.
|
|
* Unready: Unlight torch.
|
|
* Get: Equip torch if lit
|
|
* Drop: Unlight torch
|
|
*/
|
|
bool U6UseCode::torch(Obj *obj, UseCodeEvent ev) {
|
|
if (ev == USE_EVENT_USE) {
|
|
if (obj->frame_n == 1) {
|
|
extinguish_torch(obj);
|
|
return true;
|
|
}
|
|
// light
|
|
if (obj->is_on_map()) {
|
|
Obj *torch = obj_manager->get_obj_from_stack(obj, 1);
|
|
if (torch != obj)
|
|
obj_manager->add_obj(torch, true); // keep new one on map
|
|
light_torch(torch);
|
|
return true;
|
|
} else { // so is readied or in inventory
|
|
Obj *torch = obj;
|
|
Actor *actor;
|
|
if (obj->is_in_inventory() == false) // container on map
|
|
actor = actor_manager->get_player();
|
|
else
|
|
actor = actor_manager->get_actor_holding_obj(obj);
|
|
bool can_light_it = true; // only set FALSE on some error
|
|
bool in_container = obj->is_in_container();
|
|
|
|
if (!obj->is_readied()) {
|
|
torch = obj_manager->get_obj_from_stack(obj, 1);
|
|
if (torch != obj) // keep new one in inventory
|
|
actor->inventory_add_object_nostack(torch);
|
|
|
|
// ready it
|
|
// actor = actor_manager->get_actor_holding_obj(torch);
|
|
can_light_it = actor->add_readied_object(torch);
|
|
}
|
|
|
|
if (can_light_it) { // assume torch is readied
|
|
assert(torch->is_readied());
|
|
light_torch(torch);
|
|
} else {
|
|
assert(torch->qty == 1);
|
|
if (in_container) // need old location
|
|
obj_manager->moveto_container(torch, obj->get_container_obj());
|
|
else if (torch->is_in_inventory()) { // assume it's not stacked
|
|
actor->inventory_remove_obj(torch);
|
|
actor->inventory_add_object(torch); // restack here
|
|
}
|
|
scroll->display_string("\nNo free hand to hold the torch.\n");
|
|
}
|
|
}
|
|
} else if (ev == USE_EVENT_READY) {
|
|
if (obj->is_readied()) { // remove
|
|
if (obj->frame_n == 1) {
|
|
extinguish_torch(obj);
|
|
return false; // destroyed
|
|
}
|
|
} else { // equip (get one from the stack)
|
|
if (obj->qty > 1 && obj->frame_n == 0) { // don't change the quantity of lit torches
|
|
Obj *torch = obj_manager->get_obj_from_stack(obj, obj->qty - 1);
|
|
assert(torch != obj); // got a new object from the obj stack
|
|
if (obj->is_in_container())
|
|
obj_manager->moveto_container(torch, obj->get_container_obj(), false);
|
|
else if (obj->is_in_inventory()) { // keep extras in inventory
|
|
actor_manager->get_actor_holding_obj(torch)->inventory_add_object_nostack(torch);
|
|
}
|
|
}
|
|
}
|
|
return true; // equip or remove to inventory
|
|
} else if (ev == USE_EVENT_GET) {
|
|
if (obj->frame_n == 0) // unlit: may get normally
|
|
return true;
|
|
toggle_frame(obj); // unlight
|
|
obj->qty = 1;
|
|
obj_manager->remove_obj_from_map(obj); // add to inventory and USE
|
|
items.actor_ref->inventory_add_object(obj); // will unstack in USE
|
|
scroll->display_string("\n");
|
|
torch(obj, USE_EVENT_USE);
|
|
return false; // ready or not, handled by usecode
|
|
} else if (ev == USE_EVENT_DROP) {
|
|
if (obj->frame_n == 0) // unlit: normal drop
|
|
return true;
|
|
extinguish_torch(obj);
|
|
return false; // destroyed
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Torches disappear when extinguished. */
|
|
void U6UseCode::extinguish_torch(Obj *obj) {
|
|
assert(obj->frame_n == 1);
|
|
|
|
// handled by Actor::inventory_remove_obj()
|
|
// if(obj->is_in_inventory_old())
|
|
// actor_manager->get_actor_holding_obj(obj)->subtract_light(TORCH_LIGHT_LEVEL);
|
|
if (obj->is_readied()) {
|
|
Actor *owner = actor_manager->get_actor_holding_obj(obj);
|
|
if ((owner->is_in_party() || owner == player->get_actor()) && owner->is_alive()) {
|
|
if (owner->get_hp() == 0) { // Avatar during Kal Lor item removal
|
|
owner->remove_readied_object(obj, false);
|
|
party->subtract_light_source();
|
|
game->get_map_window()->updateBlacking();
|
|
return;
|
|
}
|
|
} else { // don't extinguish on death or leaving the party
|
|
game->get_map_window()->updateBlacking(); // might need this on death
|
|
return;
|
|
}
|
|
}
|
|
|
|
scroll->display_string("\nA torch burned out.\n");
|
|
destroy_obj(obj, 0, false);
|
|
game->get_map_window()->updateBlacking();
|
|
}
|
|
|
|
void U6UseCode::light_torch(Obj *obj) {
|
|
assert(obj->qty == 1);
|
|
assert(obj->frame_n == 0);
|
|
assert(obj->is_readied() || obj->is_on_map());
|
|
toggle_frame(obj); // light
|
|
obj->status |= OBJ_STATUS_LIT;
|
|
Actor *owner = nullptr;
|
|
if (obj->is_readied()) {
|
|
owner = actor_manager->get_actor_holding_obj(obj);
|
|
owner->add_light(TORCH_LIGHT_LEVEL);
|
|
}
|
|
|
|
obj->qty = 0xc8; //torch duration. updated in lua advance_time()
|
|
if (!owner || owner->is_in_party() || owner == player->get_actor())
|
|
scroll->display_string("\nTorch is lit.\n");
|
|
game->get_map_window()->updateBlacking();
|
|
}
|
|
|
|
bool U6UseCode::process_effects(Obj *container_obj, Actor *actor) {
|
|
Obj *temp_obj;
|
|
U6Link *obj_link, *temp_link;
|
|
|
|
/* Test whether this object has items inside it. */
|
|
if (container_obj->container != nullptr) {
|
|
for (obj_link = container_obj->container->end(); obj_link != nullptr;) {
|
|
temp_obj = (Obj *)obj_link->data;
|
|
|
|
if (temp_obj->obj_n == OBJ_U6_EFFECT) {
|
|
temp_link = obj_link->prev;
|
|
game->get_script()->call_actor_use_effect(temp_obj, actor); //Note this call unlinks the effect object.
|
|
obj_link = temp_link;
|
|
} else
|
|
obj_link = obj_link->prev;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Use: Display Peer effect, showing a map of the area around the player.
|
|
Message: Delete 1 gem. */
|
|
bool U6UseCode::use_peer_gem(Obj *obj, UseCodeEvent ev) {
|
|
if (ev == USE_EVENT_MESSAGE && *items.msg_ref == MESG_EFFECT_COMPLETE) {
|
|
destroy_obj(obj, 1);
|
|
scroll->display_string("\n");
|
|
scroll->display_prompt();
|
|
return true;
|
|
}
|
|
|
|
if (ev != USE_EVENT_USE)
|
|
return true;
|
|
|
|
uint16 x, y;
|
|
uint8 z;
|
|
player->get_location(&x, &y, &z);
|
|
game->get_event()->close_gumps();
|
|
new PeerEffect(x - (x % 8) - 18, y - (y % 8) - 18, z, obj); // wrap to chunk boundary,
|
|
// and center in 11x11 MapWindow
|
|
return false; // no prompt
|
|
}
|
|
|
|
/* Ready: Apply ring's status effect to actor.
|
|
Unready: Cancel status effect. */
|
|
bool U6UseCode::magic_ring(Obj *obj, UseCodeEvent ev) {
|
|
Actor *actor = obj->get_actor_holding_obj();
|
|
if (!actor)
|
|
actor = player->get_actor();
|
|
if (actor->inventory_get_readied_object(ACTOR_HAND) != nullptr
|
|
&& actor->inventory_get_readied_object(ACTOR_HAND) != obj
|
|
&& actor->inventory_get_readied_object(ACTOR_HAND_2) != nullptr
|
|
&& actor->inventory_get_readied_object(ACTOR_HAND_2) != obj)
|
|
return true;
|
|
uint8 num_readied = actor->count_readied_objects(obj->obj_n, 0);
|
|
// if(obj->obj_n == OBJ_U6_REGENERATION_RING)
|
|
// actor_manager->get_actor_holding_obj(obj)->??? no visual effect
|
|
// if(obj->obj_n == OBJ_U6_PROTECTION_RING)
|
|
// actor_manager->get_actor_holding_obj(obj)->??? no visual effect
|
|
if (obj->obj_n == OBJ_U6_INVISIBILITY_RING)
|
|
actor->set_invisible((obj->is_readied() && num_readied == 1) ? false : true);
|
|
return true; // do normal ready/unready
|
|
}
|
|
|
|
bool U6UseCode::storm_cloak(Obj *obj, UseCodeEvent ev) {
|
|
Actor *actor = obj->get_actor_holding_obj();
|
|
if (!actor)
|
|
actor = player->get_actor();
|
|
if (actor->inventory_get_readied_object(ACTOR_BODY) != nullptr
|
|
&& actor->inventory_get_readied_object(ACTOR_BODY) != obj)
|
|
return true;
|
|
|
|
AsyncEffect *e = new AsyncEffect(new TileBlackFadeEffect(actor, 9, 20)); //FIXME hardcoded values.
|
|
e->run();
|
|
|
|
if (obj->is_readied() == false) {
|
|
Game::get_game()->get_clock()->set_timer(GAMECLOCK_TIMER_U6_STORM, 0x14);
|
|
} else {
|
|
Game::get_game()->get_clock()->set_timer(GAMECLOCK_TIMER_U6_STORM, 0);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Unready/Drop/Move: Don't allow removal. */
|
|
bool U6UseCode::amulet_of_submission(Obj *obj, UseCodeEvent ev) {
|
|
if (obj->is_readied()) {
|
|
scroll->display_string("\nMagical energy prevents you from removing the amulet.\n");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Use: Learn Gargish! */
|
|
bool U6UseCode::gargish_vocabulary(Obj *obj, UseCodeEvent ev) {
|
|
if (ev == USE_EVENT_USE) {
|
|
scroll->display_string("\n");
|
|
scroll->display_string("You study the scroll!\n");
|
|
player->set_gargish_flag(true);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* LOOK: Print the name of a holy brazier, and not the normal description. */
|
|
bool U6UseCode::holy_flame(Obj *obj, UseCodeEvent ev) {
|
|
if (obj->quality == 0 || obj->quality > 3)
|
|
return true; // use normal description
|
|
scroll->display_string("\nThe flame of ");
|
|
if (obj->quality == 1)
|
|
scroll->display_string("truth");
|
|
if (obj->quality == 2)
|
|
scroll->display_string("love");
|
|
if (obj->quality == 3)
|
|
scroll->display_string("courage");
|
|
scroll->display_string(".\n");
|
|
return false;
|
|
}
|
|
|
|
bool U6UseCode::cannot_unready(const Obj *obj) const {
|
|
if (!obj->is_readied())
|
|
return false;
|
|
if (obj->obj_n == OBJ_U6_AMULET_OF_SUBMISSION
|
|
|| (obj->obj_n == OBJ_U6_TORCH && obj->frame_n == 1))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
bool U6UseCode::use_harpsichord(Obj *obj, UseCodeEvent ev) {
|
|
if (ev == USE_EVENT_SEARCH) {
|
|
return search_container(obj);
|
|
}
|
|
return play_instrument(obj, ev);
|
|
}
|
|
|
|
} // End of namespace Nuvie
|
|
} // End of namespace Ultima
|