/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "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_list = game->get_egg_manager()->get_egg_list(); Std::list::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