Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,493 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_ANIM_MANAGER_H
#define NUVIE_CORE_ANIM_MANAGER_H
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/gui/widgets/map_window.h"
#include "ultima/nuvie/core/timed_event.h"
#include "ultima/nuvie/misc/call_back.h"
#include "ultima/nuvie/misc/map_entity.h"
#include "ultima/nuvie/misc/u6_line_walker.h"
namespace Ultima {
namespace Nuvie {
using Std::list;
using Std::string;
using Std::vector;
class Actor;
class CallBack;
class AnimManager;
class NuvieAnim;
class Screen;
class Font;
#define MESG_TIMED CB_TIMED
typedef Std::list<NuvieAnim *>::iterator AnimIterator;
/* Each viewable area has it's own AnimManager. (but I can only think of
* animations in the MapWindow using this, so that could very well change)
*/
class AnimManager {
MapWindow *map_window;
Screen *viewsurf;
Common::Rect viewport; // clip anims to location
Std::list<NuvieAnim *> anim_list; // in paint order
uint32 next_id;
uint8 tile_pitch;
sint16 mapwindow_x_offset;
sint16 mapwindow_y_offset;
AnimIterator get_anim_iterator(uint32 anim_id);
public:
AnimManager(sint16 x, sint16 y, Screen *screen = nullptr, Common::Rect *clipto = nullptr);
~AnimManager() {
destroy_all();
}
void update();
void display(bool top_anims = false);
Screen *get_surface() {
return viewsurf;
}
void set_surface(Screen *screen) {
viewsurf = screen;
}
void set_area(Common::Rect clipto) {
viewport = clipto;
}
void set_tile_pitch(uint8 p) {
tile_pitch = p;
}
uint8 get_tile_pitch() const {
return tile_pitch;
}
//new_anim(new ExplosiveAnim(speed));
sint32 new_anim(NuvieAnim *new_anim);
void destroy_all();
bool destroy_anim(uint32 anim_id);
bool destroy_anim(NuvieAnim *anim_pt);
NuvieAnim *get_anim(uint32 anim_id);
void drawTile(const Tile *tile, uint16 x, uint16 y);
void drawTileAtWorldCoords(const Tile *tile, uint16 wx, uint16 wy, uint16 add_x = 0, uint16 add_y = 0);
void drawText(Font *font, const char *text, uint16 x, uint16 y);
};
/* Contains methods to support management, continuous display, and movement of
* animation across viewport.
*/
/* FIXME: The return of update() is not very useful. If an anim isn't
* redrawn then it just disappears on next MapWindow::Display(). If you don't
* want it to appear just delete it.*/
class NuvieAnim: public CallBack {
protected:
friend class AnimManager;
AnimManager *anim_manager; // set by anim_manager when adding to list
uint32 id_n; // unique
sint32 vel_x, vel_y; // movement across viewport (pixels/second; min=10)
uint32 px, py; // location on surface
uint32 last_move_time; // last time when update_position() moved (ticks)
bool safe_to_delete; // can animmgr delete me?
bool updated; // call display
bool running;
bool paused;
bool top_anim; //animate on top of mapwindow.
// return false if animation doesn't need redraw
virtual bool update() {
return true;
}
virtual void display() = 0;
void update_position();
public:
NuvieAnim();
~NuvieAnim() override;
void pause() {
paused = true;
}
void unpause() {
paused = false;
}
bool is_paused() const {
return paused;
}
virtual MapCoord get_location() {
return MapCoord(px, py, 0);
}
uint32 get_id() const {
return id_n;
}
void set_safe_to_delete(bool val) {
safe_to_delete = val;
}
void set_velocity(sint32 sx, sint32 sy) {
vel_x = sx;
vel_y = sy;
}
void set_velocity_for_speed(sint16 xdir, sint16 ydir, uint32 spd);
virtual void stop() {
updated = running = false;
}
virtual void start() { }
uint16 message(uint16 msg, void *msg_data = nullptr, void *my_data = nullptr) {
if (callback_target) return (CallBack::message(msg, msg_data, my_data));
else return 0;
}
virtual void move(uint32 x, uint32 y, uint32 add_x = 0, uint32 add_y = 0) {
px = x;
py = y;
}
virtual void shift(sint32 sx, sint32 sy) {
px += sx;
py += sy;
}
// void set_flags();
// ANIM_ONTOP
// ANIM_ONBOTTOM
};
/* Tile placement & data for TileAnim
*/
typedef struct {
sint16 pos_x, pos_y; // map position relative to Anim tx,ty
uint16 px, py; // pixel offset from pos_x,pos_y
Tile *tile;
} PositionedTile;
/* Animation using game tiles
*/
class TileAnim : public NuvieAnim {
protected:
MapWindow *_mapWindow;
uint32 _tx, _ty, // location on surface: in increments of "tile_pitch"
_px, _py; // location on surface: pixel offset from tx,ty
vector<PositionedTile *> _tiles;
void display() override;
public:
TileAnim();
~TileAnim() override;
MapCoord get_location() override {
return MapCoord(_tx, _ty, 0);
}
void get_offset(uint32 &x_add, uint32 &y_add) const {
x_add = _px;
y_add = _py;
}
sint32 get_tile_id(PositionedTile *find_tile);
void move(uint32 x, uint32 y, uint32 add_x = 0, uint32 add_y = 0) override {
_tx = x;
_ty = y;
_px = add_x;
_py = add_y;
}
void shift(sint32 sx, sint32 sy) override;
void shift_tile(uint32 ptile_num, sint32 sx, sint32 sy);
void move_tile(PositionedTile *ptile, uint32 x, uint32 y);
PositionedTile *add_tile(Tile *tile, sint16 x, sint16 y, uint16 add_x = 0, uint16 add_y = 0);
void remove_tile(uint32 i = 0);
void remove_tile(PositionedTile *p_tile);
};
/* TileAnim using a timed event.
*/
class TimedAnim: public TileAnim {
protected:
TimedCallback *timer;
public:
TimedAnim() {
timer = nullptr;
}
~TimedAnim() override {
stop_timer();
}
void start_timer(uint32 delay) {
if (!timer) timer = new TimedCallback(this, nullptr, delay, true);
}
void stop_timer() {
if (timer) {
timer->clear_target();
timer = nullptr;
}
}
void stop() override {
stop_timer();
NuvieAnim::stop();
}
};
// OR these together to tell a TossAnim what to intercept
#define TOSS_TO_BLOCKING 0x01
#define TOSS_TO_ACTOR 0x02
#define TOSS_TO_OBJECT 0x04
/* A TileAnim that can intercept objects in the world. Start selected tile at
* source, and move across viewport to target. The tile is rotated by the
* degrees argument passed to the constructor.
*/
class TossAnim : public TileAnim {
protected:
ActorManager *actor_manager;
ObjManager *obj_manager;
Map *map;
MapCoord *src, *target;
uint32 start_px, start_py, target_px, target_py;
uint8 mapwindow_level; // level of map being viewed
uint16 speed; // movement speed in pixels per second (X and Y speed can't be set independently)
Tile *toss_tile;
uint8 blocking; // stop_flags
uint8 tile_center; // tile_pitch / 2
float tanS; // Ydiff/Xdiff, between src and target (for movement velocity)
sint16 old_relpos; // when moving diagonally, last relative position on minor axis
float x_left, y_left; // when unable to move in a call, fractional movement values are collected here
uint16 x_dist, y_dist; // distances from start->target on X-axis & Y-axis
bool update() override;
MapCoord get_location() override;
void display() override;
public:
TossAnim(const Tile *tile, const MapCoord &start, const MapCoord &stop, uint16 pixels_per_sec, uint8 stop_flags = 0);
TossAnim(Obj *obj, uint16 degrees, const MapCoord &start, const MapCoord &stop, uint16 pixels_per_sec, uint8 stop_flags = 0);
~TossAnim() override;
void init(const Tile *tile, uint16 degrees, const MapCoord &start, const MapCoord &stop, uint16 pixels_per_sec, uint8 stop_flags);
void start() override;
void stop() override;
uint32 update_position(uint32 max_move = 0);
inline void accumulate_moves(float moves, sint32 &x_move, sint32 &y_move, sint8 xdir, sint8 ydir);
// Virtual functions are called when the tile hits something.
virtual void hit_target();
virtual void hit_object(Obj *obj);
virtual void hit_actor(Actor *actor);
virtual void hit_blocking(const MapCoord &obj_loc);
};
// This is for off-center tiles. The tile will be moved down by the
// shift amount if moving right, and up if moving left. (and rotated)
struct tossanim_tile_shifts_s {
uint16 tile_num;
sint8 shift; // plus or minus vertical position
};
extern const struct tossanim_tile_shifts_s tossanim_tile_shifts[];
/* a line of fire */
typedef struct {
PositionedTile *tile; // last associated sprite
MapCoord direction; // where the explosion sprites are going
uint32 travelled; // distance this fire line has travelled
} ExplosiveAnimSegment;
/* SuperBomberman! Toss fireballs in multiple directions from source out.
*/
class ExplosiveAnim : public TimedAnim {
MapCoord center;
uint32 radius; // num. of spaces from center
vector<ExplosiveAnimSegment> flame; // lines of fire from the center
uint16 exploding_tile_num; // fireball effect tile_num
vector<MapEntity> hit_items; // things the explosion has hit
public:
ExplosiveAnim(const MapCoord &start, uint32 size);
~ExplosiveAnim() override;
void start() override;
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
bool update() override;
bool already_hit(const MapEntity &ent);
void hit_object(Obj *obj);
void hit_actor(Actor *actor);
void get_shifted_location(uint16 &x, uint16 &y, uint16 &px, uint16 &py,
uint32 sx, uint32 sy);
};
typedef struct {
MapCoord target;
U6LineWalker *lineWalker;
PositionedTile *p_tile;
uint8 update_idx;
uint16 rotation;
uint16 rotation_amount;
float current_deg;
bool isRunning;
} ProjectileLine;
class ProjectileAnim : public TileAnim {
MapCoord src;
vector<ProjectileLine> line;
uint16 tile_num; // fireball effect tile_num
uint8 src_tile_y_offset; //amount to offset src_tile when rotating. Used by arrows and bolts
vector<MapEntity> hit_items; // things the projectile has hit
uint16 stopped_count;
uint8 speed; //number of pixels to move in a single update.
bool leaveTrailFlag;
public:
ProjectileAnim(uint16 tileNum, MapCoord *start, vector<MapCoord> target, uint8 animSpeed, bool leaveTrailFlag = false, uint16 initialTileRotation = 0, uint16 rotationAmount = 0, uint8 src_y_offset = 0);
~ProjectileAnim() override;
void start() override;
bool update() override;
protected:
void hit_entity(MapEntity entity);
bool already_hit(const MapEntity &ent);
};
class WingAnim : public TileAnim {
MapCoord target;
sint32 x, y, finish_x;
sint16 x_inc;
Tile *wing_top[2];
Tile *wing_bottom[2];
PositionedTile *p_tile_top;
PositionedTile *p_tile_bottom;
public:
WingAnim(const MapCoord &target);
~WingAnim() override;
void start() override;
bool update() override;
};
typedef struct {
uint16 x, y;
PositionedTile *p_tile;
uint8 length_left;
} Hailstone;
#define HAILSTORM_ANIM_MAX_STONES 6
class HailstormAnim : public TileAnim {
MapCoord target;
Tile *hailstone_tile;
Hailstone hailstones[HAILSTORM_ANIM_MAX_STONES];
uint8 num_hailstones_left;
uint8 num_active;
public:
HailstormAnim(const MapCoord &t);
~HailstormAnim() override;
void start() override;
bool update() override;
protected:
sint8 find_free_hailstone();
};
/* Display hit effect over an actor or location for a certain duration.
*/
class HitAnim : public TimedAnim {
Actor *hit_actor;
bool update() override;
public:
HitAnim(const MapCoord &loc);
HitAnim(Actor *actor);
uint16 callback(uint16 msg, CallBack *caller, void *msg_data) override;
void start() override {
start_timer(300);
}
};
class TextAnim : public TimedAnim {
Std::string text;
Font *font;
uint32 duration;
public:
TextAnim(Std::string text, MapCoord loc, uint32 dur);
~TextAnim() override;
uint16 callback(uint16 msg, CallBack *caller, void *msg_data) override;
void start() override {
start_timer(duration);
}
void display() override;
};
class TileFadeAnim : public TileAnim {
uint16 pixel_count;
Tile *anim_tile;
Tile *to_tile;
bool should_delete_to_tile;
uint16 pixels_per_update; //the number of pixels to change in each update.
unsigned char mask[256];
public:
TileFadeAnim();
TileFadeAnim(const MapCoord &loc, Tile *from, Tile *to, uint16 speed);
TileFadeAnim(const MapCoord &loc, Tile *from, uint8 color_from, uint8 color_to, bool reverse, uint16 speed);
~TileFadeAnim() override;
bool update() override;
protected:
void init(uint16 speed);
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,58 @@
/* 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/shared/std/string.h"
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/conf/configuration.h"
#include "ultima/nuvie/files/u6_lib_n.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/core/book.h"
namespace Ultima {
namespace Nuvie {
Book::Book(const Configuration *cfg) : config(cfg), books(new U6Lib_n) {
}
Book::~Book() {
delete books;
}
bool Book::init() {
Common::Path filename;
config_get_path(config, "book.dat", filename);
if (books->open(filename, 2) == false)
return false;
return true;
}
char *Book::get_book_data(uint16 num) {
if (num >= books->get_num_items())
return nullptr;
return reinterpret_cast<char *>(books->get_item(num));
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,48 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_BOOK_H
#define NUVIE_CORE_BOOK_H
namespace Ultima {
namespace Nuvie {
class Configuration;
class U6Lib_n;
class Book {
const Configuration *config;
U6Lib_n *books;
public:
Book(const Configuration *cfg);
~Book();
bool init();
char *get_book_data(uint16 num);
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,752 @@
/* 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/core/game.h"
#include "ultima/nuvie/conf/configuration.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/files/u6_lzw.h"
#include "ultima/nuvie/core/player.h"
#include "ultima/nuvie/core/party.h"
#include "ultima/nuvie/views/view_manager.h"
#include "ultima/nuvie/actors/actor_manager.h"
#include "ultima/nuvie/sound/sound_manager.h"
#include "ultima/nuvie/core/events.h"
#include "ultima/nuvie/gui/widgets/map_window.h"
#include "ultima/nuvie/core/converse_interpret.h"
#include "ultima/nuvie/core/converse_speech.h"
#include "ultima/nuvie/gui/widgets/converse_gump.h"
#include "ultima/nuvie/core/converse.h"
#include "ultima/nuvie/gui/gui.h"
#include "ultima/nuvie/gui/widgets/background.h"
namespace Ultima {
namespace Nuvie {
//#define CONVERSE_DEBUG
Converse::Converse() : config(nullptr), actors(nullptr), objects(nullptr),
player(nullptr), views(nullptr), last_view(nullptr), scroll(nullptr),
conv_i(nullptr), script(nullptr), npc(nullptr), npc_num(0), script_num(0),
src(nullptr), src_num(0), allowed_input(nullptr), active(false),
variables(nullptr), party_all_the_time(false), speech(nullptr),
using_fmtowns(false), need_input(false), conversations_stop_music(false),
gametype(NUVIE_GAME_NONE), _clock(nullptr) {
ARRAYCLEAR(aname);
}
/* Initialize global classes from the game.
*/
void
Converse::init(const Configuration *cfg, nuvie_game_t t, MsgScroll *s, ActorManager *a,
GameClock *c, Player *p, ViewManager *v, ObjManager *o) {
Std::string townsdir;
config = cfg;
scroll = s;
actors = a;
_clock = c;
player = p;
views = v;
objects = o;
gametype = t;
cfg->value("config/cheats/party_all_the_time", party_all_the_time);
cfg->value("config/audio/conversations_stop_music", conversations_stop_music, false);
cfg->value("config/townsdir", townsdir, "");
if (townsdir != "" && directory_exists(townsdir.c_str()))
using_fmtowns = true;
speech = new ConverseSpeech();
speech->init(config);
}
Converse::~Converse() {
if (running()) {
reset();
DEBUG(0, LEVEL_INFORMATIONAL, "End conversation\n");
}
unload_conv();
delete speech;
}
/* Free up allocated memory, reset values for new conversation. (call only when
* ending a conversation or quitting)
*/
void Converse::reset() {
delete conv_i;
conv_i = nullptr;
set_input(""); // delete
set_output(""); // clear output
_name = ""; // clear name
if (script) {
delete script;
script = nullptr;
}
if (allowed_input) {
free(allowed_input);
allowed_input = nullptr;
}
player->set_quest_flag((uint8)get_var(U6TALK_VAR_QUESTF));
player->set_gargish_flag((uint8)get_var(U6TALK_VAR_GARGF));
delete_variables();
}
/* Load `convfilename' as src.
*/
void Converse::load_conv(const Std::string &convfilename) {
Common::Path conv_lib_str;
if (gametype == NUVIE_GAME_U6 && using_fmtowns) {
config->pathFromValue("config/townsdir", convfilename, conv_lib_str);
} else {
config_get_path(config, convfilename, conv_lib_str);
}
unload_conv();
src_num = 0;
if (gametype == NUVIE_GAME_U6) {
src = new U6Lib_n;
src->open(conv_lib_str, 4);
src_num = (convfilename == "converse.a") ? 1 : (convfilename == "converse.b") ? 2 : 0;
} else { // MD or SE gametype
src = new U6Lib_n;
src->open(conv_lib_str, 4, gametype);
src_num = 1;
}
#ifdef CONVERSE_DEBUG
DEBUG(0, LEVEL_DEBUGGING, "Converse: load \"%s\"\n", convfilename.c_str());
#endif
}
uint32 Converse::get_script_num(uint8 a) {
if (gametype == NUVIE_GAME_U6) {
if (a > 200) { // (quick fix for U6: anything over 200 is a temporary npc)
Actor *npcP = actors->get_actor(a);
if (npcP->get_obj_n() == 373) // OBJ_U6_WISP
a = 201;
else if (npcP->get_obj_n() == 382) // OBJ_U6_GUARD
a = 202;
}
//else if(a == 188) // U6: temp. fix for shrines
// a = 191; // ??? -> Exodus
//else if(a >= 191 && a <= 197) // shrines except spirituality & humility
// a += 2;
//else if(a == 198)
// a = 192; // Spirituality -> Honesty
//else if(a == 199)
// a = 200; // Humility -> Singularity
}
return a;
}
/* Check that loaded converse library (if any) has script for npc `a'. Load
* another file if it doesn't.
* Returns the real item number in the source.
*/
uint32 Converse::load_conv(uint8 a) {
if (gametype == NUVIE_GAME_U6) {
if (a <= 98) {
if (src_num != 1)
load_conv("converse.a");
} else { // a >= 99
if (src_num != 2)
load_conv("converse.b");
}
} else {
if (src_num != 1)
load_conv("talk.lzc");
}
// we want to return the real item number in the converse file.
if (gametype == NUVIE_GAME_U6 && a > 98) {
a -= 99;
} else if (gametype == NUVIE_GAME_SE) {
a -= 2;
}
return a;
}
/* Returns name of loaded source file, identified by `src_num'.
*/
const char *Converse::src_name() {
if (src_num == 0)
return "";
if (gametype == NUVIE_GAME_U6)
return ((src_num == 1) ? "converse.a" : "converse.b");
if (gametype == NUVIE_GAME_MD)
return "talk.lzc";
if (gametype == NUVIE_GAME_SE)
return "talk.lzc";
return "";
}
/* Get an NPC conversation from the source file.
* Returns new ConvScript object.
*/
ConvScript *Converse::load_script(uint32 n) {
ConvScript *loaded = new ConvScript(src, n);
if (!loaded->loaded()) {
delete loaded;
loaded = nullptr;
} else
DEBUG(0, LEVEL_INFORMATIONAL, "Read %s npc script (%s:%d)\n",
loaded->compressed ? "encoded" : "unencoded", src_name(), (unsigned int)n);
return loaded;
}
/* Initialize Converse variable list, and set globals from game.
*/
void Converse::init_variables() {
if (variables)
delete_variables();
variables = new converse_variables_s[U6TALK_VAR__LAST_ + 1];
for (uint32 v = 0; v <= U6TALK_VAR__LAST_; v++) {
variables[v].cv = 0;
variables[v].sv = nullptr;
}
set_var(U6TALK_VAR_SEX, player->get_gender());
set_var(U6TALK_VAR_KARMA, player->get_karma());
set_var(U6TALK_VAR_GARGF, player->get_gargish_flag());
set_var(U6TALK_VAR_PARTYLIVE, player->get_party()->get_party_size() - 1);
// FIXME: count dead party members in PARTYALL, not in PARTYLIVE
set_var(U6TALK_VAR_PARTYALL, get_var(U6TALK_VAR_PARTYLIVE));
set_var(U6TALK_VAR_HP, player->get_actor()->get_hp());
set_svar(U6TALK_VAR_NPC_NAME, npc_name(npc_num));
set_svar(U6TALK_VAR_PLAYER_NAME, player->get_name());
set_var(U6TALK_VAR_QUESTF, player->get_quest_flag());
set_var(U6TALK_VAR_WORKTYPE, npc->get_worktype());
}
/* Free memory used by Converse variable list.
*/
void Converse::delete_variables() {
for (uint32 v = 0; v <= U6TALK_VAR__LAST_; v++)
if (variables[v].sv)
free(variables[v].sv);
delete [] variables;
variables = nullptr;
}
/* Create new script interpreter for the current game.
* Returns pointer to object which is derived from ConverseInterpret.
*/
ConverseInterpret *Converse::new_interpreter() {
ConverseInterpret *ci = nullptr;
switch (gametype) {
case NUVIE_GAME_U6:
ci = (ConverseInterpret *)new U6ConverseInterpret(this);
break;
case NUVIE_GAME_MD:
ci = (ConverseInterpret *)new MDTalkInterpret(this);
break;
case NUVIE_GAME_SE:
ci = (ConverseInterpret *)new SETalkInterpret(this);
break;
}
return ci;
}
/* Returns false if a conversation cannot be started with the NPC. This
* represents an internal error, and doesn't have anything to do with the NPC
* not wanting/being able to talk to the Avatar.
*/
bool Converse::start(uint8 n) {
uint32 real_script_num = 0; // The script number in the converse file.
// load, but make sure previous script is unloaded first
if (running())
stop();
if (!(npc = actors->get_actor(n)))
return false;
// get script num for npc number (and open file)
script_num = get_script_num(n);
real_script_num = load_conv(script_num);
if (!src)
return false;
script = load_script(real_script_num);
// begin
if (script) {
active = true;
last_view = views->get_current_view();
if (!(conv_i = new_interpreter())) {
DEBUG(0, LEVEL_CRITICAL, "Can't talk: Unimplemented or unknown game type\n");
return false;
}
views->close_all_gumps();
// set current NPC and start conversation
npc_num = n;
init_variables();
scroll->set_talking(true, actors->get_actor(npc_num));
Game::get_game()->get_map_window()->set_walking(false);
Game::get_game()->get_map_window()->set_looking(false);
if (conversations_stop_music)
Game::get_game()->get_sound_manager()->musicStop();
//Game::get_game()->get_event()->set_mode(WAIT_MODE); // ignore player actions
Game::get_game()->pause_user();
Game::get_game()->get_gui()->unblock();
scroll->set_autobreak(true);
/* moved into ConverseGump::set_talking()
if(Game::get_game()->is_new_style())
{
scroll->Show();
scroll->set_input_mode(false);
scroll->clear_scroll();
((ConverseGump *)scroll)->set_found_break_char(true);
//scroll->grab_focus();
}
*/
show_portrait(npc_num);
unwait();
DEBUG(0, LEVEL_INFORMATIONAL, "Begin conversation with \"%s\" (npc %d)\n", npc_name(n), n);
return true;
}
DEBUG(0, LEVEL_ERROR, "Failed to load npc %d from %s:%d\n",
n, src_name(), script_num);
return false;
}
/* Stop execution of the current script.
*/
void Converse::stop() {
scroll->set_talking(false);
MsgScroll *system_scroll = Game::get_game()->get_scroll();
if ((Game::get_game()->using_new_converse_gump() || scroll != system_scroll) && !scroll->is_converse_finished()) {
return;
}
reset(); // free memory
if (Game::get_game()->using_new_converse_gump()) {
scroll->Hide();
if (!Game::get_game()->is_new_style()) {
Game::get_game()->get_event()->endAction(true);
GUI::get_gui()->force_full_redraw(); // need to remove converse background
}
} else {
system_scroll->set_autobreak(false);
system_scroll->display_string("\n");
system_scroll->display_prompt();
if (scroll != system_scroll) { //if using an alternate scroll eg wou fullmap scroll.
scroll->Hide();
}
}
if (!Game::get_game()->is_new_style()) {
if (last_view->set_party_member(last_view->get_party_member_num()) == false) // set party member left party
last_view->prev_party_member(); // seems only needed with new converse gump but will leave here just in case
views->set_current_view(last_view);
}
Game::get_game()->unpause_user();
if (conversations_stop_music) {
SoundManager *sm = Game::get_game()->get_sound_manager();
if (sm->is_audio_enabled() && sm->is_music_enabled())
sm->musicPlay();
}
Game::get_game()->get_event()->set_mode(MOVE_MODE); // return control to player
active = false;
DEBUG(0, LEVEL_INFORMATIONAL, "End conversation\n");
}
/* Returns true if there is input available (placed at `in_str'.)
*/
bool Converse::input() {
if (scroll->has_input()) {
Std::string s = scroll->get_input();
set_input(s);
#ifdef CONVERSE_DEBUG
DEBUG(0, LEVEL_DEBUGGING, "Converse: INPUT \"%s\"\n\n", get_input().c_str());
#endif
return true;
}
return false;
}
/* Output string `s' or the current set output to the scroll view.
*/
void Converse::print(const char *s) {
#ifdef CONVERSE_DEBUG
DEBUG(0, LEVEL_DEBUGGING, "Converse: PRINT \"%s\"\n\n", s ? s : get_output().c_str());
#endif
if (s)
scroll->display_string(s, MSGSCROLL_NO_MAP_DISPLAY);
else
scroll->display_string(get_output(), MSGSCROLL_NO_MAP_DISPLAY);
}
void Converse::print_prompt() {
scroll->display_converse_prompt();
}
/* Get string value of variable `varnum'.
*/
const char *Converse::get_svar(uint8 varnum) {
if (varnum <= U6TALK_VAR__LAST_ && variables[varnum].sv)
return (const char *)variables[varnum].sv;
return "";
}
/* Set string value of variable `varnum'.
*/
void Converse::set_svar(uint8 varnum, const char *set) {
if (varnum <= U6TALK_VAR__LAST_)
variables[varnum].sv = scumm_strdup(set);
}
/* Show portrait for npc `n'. The name will be shown for actors in the player
* party, or those the player/avatar has met. The look-string will be shown for
* anyone else.
*/
void Converse::show_portrait(uint8 n) {
Game *game = Game::get_game();
Actor *actor = (n == npc_num) ? npc : actors->get_actor(n);
const char *nameret = nullptr;
if (!actor)
return;
bool statue = (gametype == NUVIE_GAME_U6 && n >= 189 && n <= 191);
if (gametype == NUVIE_GAME_U6 && n == 0) { // Pushme Pullyu
Actor *real_actor = game->get_actor_manager()->get_actor(130);
if (real_actor->is_met() || player->get_party()->contains_actor(real_actor))
nameret = npc_name(130);
else
nameret = actors->look_actor(real_actor, false);
} else if ((actor->is_met() || player->get_party()->contains_actor(actor))
&& !statue) // they need to display statue of names
nameret = npc_name(n);
else
nameret = actors->look_actor(actor, false);
if (game->using_new_converse_gump()) {
if ((game->is_original_plus() && game->get_converse_gump()->W() > game->get_game_width() - game->get_background()->get_border_width())
|| game->is_orig_style())
views->close_current_view();
((ConverseGump *)scroll)->set_actor_portrait(actor);
} else
views->set_portrait_mode(actor, nameret);
}
/* Copy the NPC num's name from their conversation script. This is very U6
* specific.
* Returns the name as a non-modifiable string of 16 characters maximum. */
const char *Converse::npc_name(uint8 num) {
ConvScript *temp_script;
convscript_buffer s_pt;
aname[15] = '\0';
// FIX (crashing)
// if(actors->get_actor(num))
// actors->get_actor(num)->set_name(name);
if ((num == npc_num) && !_name.empty()) // use NPC name
strncpy(aname, _name.c_str(), 15);
else { // or load another script
// uint32 temp_num = num;
num = load_conv(get_script_num(num)); // get idx number; won't actually reload file
temp_script = new ConvScript(src, num);
s_pt = temp_script->get_buffer();
if (!s_pt) {
delete temp_script;
return nullptr;
}
// read name up to LOOK section, convert "_" to "."
uint32 c;
for (c = 0; s_pt[c + 2] != 0xf1 && s_pt[c + 2] != 0xf3 && c <= 14; c++)
aname[c] = s_pt[c + 2] != '_' ? s_pt[c + 2] : '.';
aname[c] = '\0';
delete temp_script;
}
return aname;
}
/* Start checking i/o object for some input, (optionally block all but allowed
* input) and tell interpreter to wait.
*/
void Converse::poll_input(const char *allowed, bool nonblock) {
if (allowed_input)
free(allowed_input);
allowed_input = nullptr;
allowed_input = (allowed && strlen(allowed)) ? scumm_strdup(allowed) : nullptr;
scroll->set_input_mode(true, allowed_input, nonblock);
need_input = true;
conv_i->wait();
}
/* Stop polling i/o, tell interpreter to stop waiting.
*/
void Converse::unwait() {
need_input = false;
conv_i->unwait();
}
/* Check talk input and determine if it needs to be handled before being passed
* to the interpreter.
* Returns false if the conversation should be stopped.
*/
bool Converse::override_input() {
bool overide_cheat = Game::get_game()->are_cheats_enabled() && party_all_the_time;
if (in_str.empty())
in_str = "bye";
else if (in_str == "look") {
print("You see ");
print(_desc.c_str());
script->seek(script->pos() - 1); // back to ASK command
} else if (overide_cheat && in_str == "join") {
if (Game::get_game()->get_game_type() == NUVIE_GAME_U6 // altars and statues
&& (npc->get_actor_num() >= 189 && npc->get_actor_num() <= 200))
return true;
else if (!npc->is_alive()) {
print("\"How can I join you when I'm dead?\"\n*");
return true;
}
if (!player->get_party()->contains_actor(npc))
player->get_party()->add_actor(npc);
print("\"Friends of Nuvie? Sure, I'll come along!\"\n*");
return false;
} else if (overide_cheat && in_str == "leave") {
if (player->get_party()->contains_actor(npc))
player->get_party()->remove_actor(npc);
print("\"For Nuvie!\"\n*");
return false;
}
return true;
}
void Converse::collect_input() {
if (!Game::get_game()->using_new_converse_gump()) {
print_prompt();
}
poll_input();
}
/* If not waiting, continue the active script. If waiting for input, check i/o
* object (scroll), taking the input if available. Else wait until the scroll's
* page is unbroken.
*/
void Converse::continue_script() {
speech->update();
if (running()) {
if (!conv_i->waiting())
conv_i->step();
else if (need_input && input()) {
print("\n\n");
if (!override_input()) {
need_input = false;
conv_i->stop();
stop();
return;
}
// assign value to declared input variable
if (conv_i->var_input())
conv_i->assign_input();
set_svar(U6TALK_VAR_INPUT, get_input().c_str()); // set $Z
unwait();
} else if (!need_input && !scroll->get_page_break() && scroll->is_converse_finished()) {
// if page unbroken, unpause script
unwait();
}
// interpreter has stopped itself
if (conv_i->end())
stop();
}
}
/*** ConvScript ***/
/* Init. and read data from U6Lib.
*/
ConvScript::ConvScript(U6Lib_n *s, uint32 idx) {
buf = nullptr;
buf_len = 0;
src = s;
src_index = idx;
ref = 0;
cpy = nullptr;
read_script();
rewind();
}
/* Init. and use data from another ConvScript.
*/
ConvScript::ConvScript(ConvScript *orig) {
src = nullptr;
buf = nullptr;
buf_len = 0;
src_index = 0;
compressed = false;
cpy = orig;
ref = 1;
cpy->ref += 1;
rewind();
}
ConvScript::~ConvScript() {
if (ref == 0)
free(buf);
else if (cpy)
cpy->ref -= 1;
}
/* Read (decode if necessary) the script data (with the pre-set item index) from
* the loaded converse library.
*/
void ConvScript::read_script() {
unsigned char *undec_script = 0; // item as it appears in library
unsigned char *dec_script = 0; // decoded
uint32 undec_len = 0, dec_len = 0;
U6Lzw decoder;
uint8 gametype = src->get_game_type();
undec_len = src->get_item_size(src_index);
if (undec_len > 4) {
undec_script = src->get_item(src_index);
if (gametype == NUVIE_GAME_U6) {
// decode
if (!(undec_script[0] == 0 && undec_script[1] == 0
&& undec_script[2] == 0 && undec_script[3] == 0)) {
compressed = true;
dec_script =
decoder.decompress_buffer(undec_script, undec_len, dec_len);
free(undec_script);
} else {
compressed = false;
dec_len = undec_len - 4;
dec_script = (unsigned char *)malloc(dec_len);
memcpy(dec_script, undec_script + 4, dec_len);
free(undec_script);
}
} else {
// MD/SE compression handled by lzc library
compressed = false;
dec_len = undec_len;
dec_script = undec_script;
}
}
if (dec_len) {
buf = (convscript_buffer)dec_script;
buf_len = dec_len;
}
}
/* Returns 8bit value from current script location in LSB-first form.
*/
converse_value ConvScript::read(uint32 advance) {
uint8 val = 0;
while (advance--) {
val = *buf_pt;
++buf_pt;
}
return val;
}
/* Returns 16bit value from current script location in LSB-first form.
*/
converse_value ConvScript::read2() {
uint16 val = 0;
val = *(buf_pt++);
val += *(buf_pt++) << 8;
return val;
}
/* Returns 32bit value from current script location in LSB-first form.
*/
converse_value ConvScript::read4() {
uint32 val = 0;
val = *(buf_pt++);
val += *(buf_pt++) << 8;
val += *(buf_pt++) << 16;
val += *(buf_pt++) << 24;
return val;
}
void ConvScript::write2(converse_value val) {
*(buf_pt++) = val & 0xff;
*(buf_pt++) = (val >> 8) & 0xff;
return;
}
ConverseGumpType get_converse_gump_type_from_config(const Configuration *config) {
Std::string configvalue;
config->value("config/general/converse_gump", configvalue, "default");
if (string_i_compare(configvalue, "default")) {
return CONVERSE_GUMP_DEFAULT;
} else if (string_i_compare(configvalue, "u7style")) {
return CONVERSE_GUMP_U7_STYLE;
} else if (string_i_compare(configvalue, "wou")) {
return CONVERSE_GUMP_WOU_STYLE;
}
return CONVERSE_GUMP_DEFAULT;
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,269 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_CONVERSE_H
#define NUVIE_CORE_CONVERSE_H
#include "ultima/shared/std/string.h"
#include "ultima/shared/std/containers.h"
#include "ultima/nuvie/actors/actor.h"
#include "ultima/nuvie/gui/widgets/msg_scroll.h"
#include "ultima/nuvie/files/u6_lib_n.h"
#include "ultima/nuvie/views/view.h"
namespace Ultima {
namespace Nuvie {
class Actor;
class ActorManager;
class Configuration;
class MsgScroll;
class GameClock;
class ObjManager;
class Player;
class ViewManager;
class U6Lib_n;
class U6Lzw;
class ConverseInterpret;
class ConverseSpeech;
class ConvScript;
using Std::string;
ConverseGumpType get_converse_gump_type_from_config(const Configuration *config);
typedef uint32 converse_value; // any single value read from a script
typedef unsigned char *convscript_buffer;
typedef struct {
uint8 type;
converse_value val;
} converse_typed_value;
#define U6TALK_VAR_SEX 0x10 // sex of avatar: male=0 female=1
#define U6TALK_VAR_KARMA 0x14 // avatar's karma
#define U6TALK_VAR_GARGF 0x15 // 1=player knows Gargish
#define U6TALK_VAR_NPC_NAME 0x17
#define U6TALK_VAR_PARTYLIVE 0x17 // number of people (living) following avatar
#define U6TALK_VAR_PARTYALL 0x18 // number of people (total) following avatar
#define U6TALK_VAR_HP 0x19 // avatar's health
#define U6TALK_VAR_PLAYER_NAME 0x19
#define U6TALK_VAR_QUESTF 0x1A // 0="Thou art not upon a sacred quest!"
#define WOUTALK_VAR_ADD_TO_INVENTORY_FAILED 0x1D
#define U6TALK_VAR_WORKTYPE 0x20 // current activity of npc, from schedule
#define U6TALK_VAR_YSTRING 0x22 // value of $Y variable.
#define U6TALK_VAR_INPUT 0x23 // previous input from player ($Z)
#define U6TALK_VAR__LAST_ 0x25 // (all above 36 appear uninitialized)
/* Conversation engine, apart from the interpreter. Loads converse files,
* and reads script into buffer. Also manages input/output and has npc-related
* support functions. This class handles all game types.
*/
class Converse {
friend class ConverseInterpret;
friend class SETalkInterpret;
friend class MDTalkInterpret;
friend class WOUConverseInterpret;
friend class U6ConverseInterpret;
// game system objects from nuvie
const Configuration *config;
GameClock *_clock;
ActorManager *actors;
ObjManager *objects;
Player *player;
ViewManager *views;
MsgScroll *scroll; // i/o
nuvie_game_t gametype; // what game is being played?
U6Lib_n *src;
uint8 src_num; // identify source file: 0=unset/unused
const char *src_name();
ConverseInterpret *conv_i; // interpreter
ConvScript *script;
View *last_view;
Actor *npc;
uint8 npc_num;
uint8 script_num; //this could differ from npc_num when talking to guards or wisps etc.
Std::string _name, _desc;
bool active; // running npc script? (either paused or unpaused)
bool need_input; // waiting for text input
bool party_all_the_time; // force NPCs to join player's party?
string in_str; // last input from player
string out_str; // text that is to be printed
char *allowed_input; // characters requested for single-character input
char aname[16]; // return from npc_name()
struct converse_variables_s {
converse_value cv;
char *sv;
} *variables; /* initialized for [U6TALK_VAR__LAST_+1] items */
ConverseSpeech *speech;
bool using_fmtowns;
void reset();
public:
Converse();
~Converse();
void init(const Configuration *cfg, nuvie_game_t t, MsgScroll *s, ActorManager *a,
GameClock *c, Player *p, ViewManager *v, ObjManager *o);
uint32 get_script_num(uint8 a);
void load_conv(const Std::string &convfilename);
uint32 load_conv(uint8 a);
void unload_conv() {
delete src;
src = nullptr;
}
ConvScript *load_script(uint32 n);
ConverseInterpret *new_interpreter();
bool start(Actor *a) {
return start(a->get_actor_num());
}
bool start(uint8 n);
void continue_script();
void stop();
bool running() const {
return active;
}
bool is_waiting_for_scroll() {
return scroll->get_page_break();
}
void unwait();
void poll_input(const char *allowed = nullptr, bool nonblock = true);
bool override_input();
void collect_input();
bool input();
void print(const char *s = nullptr);
const Std::string &get_input() const {
return in_str;
}
const Std::string &get_output() const {
return out_str;
}
void set_input(Std::string s) {
in_str = s;
}
void set_output(Std::string s) {
out_str = s;
}
void set_party_all_the_time(bool val) {
party_all_the_time = val;
}
const char *npc_name(uint8 num);
void show_portrait(uint8 n);
converse_value get_var(uint8 varnum) const {
return (varnum <= U6TALK_VAR__LAST_ ? variables[varnum].cv : 0x00);
}
const char *get_svar(uint8 varnum);
void set_var(uint8 varnum, uint32 val) {
if (varnum <= U6TALK_VAR__LAST_) variables[varnum].cv = val;
}
void set_svar(uint8 varnum, const char *set);
void init_variables();
void delete_variables();
ConverseSpeech *get_speech() {
return speech;
};
bool conversations_stop_music;
private:
void print_prompt();
};
/* Conversation script container. Maintains current position in the script. The
* object only exists if it has data loaded. Different classes with an identical
* interface can be created to handle different games' file formats.
*/
class ConvScript {
friend class Converse;
convscript_buffer buf;
uint32 buf_len;
convscript_buffer buf_pt; // pointer into script (current location)
U6Lib_n *src;
uint32 src_index;
bool compressed; // was the original file (LZW) compressed?
uint8 ref; // Multiple objects can use the same buffer
ConvScript *cpy;
public:
ConvScript(U6Lib_n *s, uint32 idx);
ConvScript(ConvScript *orig);
~ConvScript();
void read_script();
bool loaded() const {
return ((buf && buf_len)); // script is loaded?
}
/* Reading */
converse_value read(uint32 advance = 1);
converse_value read2();
converse_value read4();
converse_value peek(uint32 displacement = 0) {
return ((converse_value) * (buf_pt + displacement));
}
/* Writing */
void write2(converse_value val);
/* Seeking - update script pointer */
void rewind() {
buf_pt = buf;
}
void skip(uint32 bytes = 1) {
buf_pt += bytes;
}
void seek(uint32 offset = 0) {
rewind();
skip(offset);
}
uint32 pos() const {
return buf_pt - buf;
}
bool overflow(uint32 ptadd = 0) const {
return (((pos() + ptadd) >= buf_len));
}
convscript_buffer get_buffer(uint32 ptadd = 0) {
return ((!ptadd || (ptadd < buf_len)) ? buf + ptadd : nullptr);
}
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,338 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_CONVERSE_INTERPRET_H
#define NUVIE_CORE_CONVERSE_INTERPRET_H
#include "ultima/nuvie/core/converse.h"
namespace Ultima {
namespace Nuvie {
using Std::string;
using Std::vector;
/* Control and value opcodes for op() & evop() (U6) */
#define U6OP_GT 0x81
#define U6OP_GE 0x82
#define U6OP_LT 0x83
#define U6OP_LE 0x84
#define U6OP_NE 0x85
#define U6OP_EQ 0x86
#define U6OP_ADD 0x90
#define U6OP_SUB 0x91
#define U6OP_MUL 0x92
#define U6OP_DIV 0x93
#define U6OP_LOR 0x94
#define U6OP_LAND 0x95
#define U6OP_CANCARRY 0x9a
#define U6OP_WEIGHT 0x9b
#define U6OP_HORSED 0x9d
#define U6OP_HASOBJ 0x9f
#define U6OP_RAND 0xa0
#define U6OP_EVAL 0xa7
#define U6OP_FLAG 0xab
#define U6OP_VAR 0xb2
#define U6OP_SVAR 0xb3
#define U6OP_DATA 0xb4
#define U6OP_OBJCOUNT 0xbb
#define U6OP_INPARTY 0xc6
#define U6OP_OBJINPARTY 0xc7
#define U6OP_JOIN 0xca
#define U6OP_LEAVE 0xcc
#define U6OP_NPCNEARBY 0xd7
#define U6OP_WOUNDED 0xda
#define U6OP_POISONED 0xdc
#define U6OP_NPC 0xdd
#define U6OP_EXP 0xe0
#define U6OP_LVL 0xe1
#define U6OP_STR 0xe2
#define U6OP_INT 0xe3
#define U6OP_DEX 0xe4
#define U6OP_HORSE 0x9c
#define U6OP_SLEEP 0x9e
#define U6OP_IF 0xa1
#define U6OP_ENDIF 0xa2
#define U6OP_ELSE 0xa3
#define U6OP_SETF 0xa4
#define U6OP_CLEARF 0xa5
#define U6OP_DECL 0xa6
#define U6OP_ASSIGN 0xa8
#define U6OP_JUMP 0xb0
#define U6OP_DPRINT 0xb5
#define U6OP_BYE 0xb6
#define U6OP_INDEXOF 0xb7
#define U6OP_NEW 0xb9
#define U6OP_DELETE 0xba
#define U6OP_INVENTORY 0xbe
#define U6OP_PORTRAIT 0xbf
#define U6OP_ADDKARMA 0xc4
#define U6OP_SUBKARMA 0xc5
#define U6OP_GIVE 0xc9
#define U6OP_WAIT 0xcb
#define U6OP_WORKTYPE 0xcd
#define U6OP_RESURRECT 0xd6
#define U6OP_SETNAME 0xd8
#define U6OP_HEAL 0xd9
#define U6OP_CURE 0xdb
#define U6OP_ENDANSWER 0xee
#define U6OP_KEYWORDS 0xef
#define U6OP_SLOOK 0xf1
#define U6OP_SCONVERSE 0xf2
#define U6OP_SPREFIX 0xf3
#define U6OP_ANSWER 0xf6
#define U6OP_ASK 0xf7
#define U6OP_ASKC 0xf8
#define U6OP_INPUTSTR 0xf9
#define U6OP_INPUT 0xfb
#define U6OP_INPUTNUM 0xfc
#define U6OP_SIDENT 0xff
#define U6OP_ENDDATA 0xb8
#define MDOP_MISC_ACTION 0xd1
/* Script is executed as it is stepped through byte-by-byte, and can have
* text, data, and control codes. Flow is controlled by run-level stack.
*/
class ConverseInterpret {
protected:
Converse *converse; // to get data from container
bool is_waiting; // return control to Converse, paused waiting for something
bool stopped; // conversation will end, after control returns to Converse
uint8 answer_mode; // should a response has been triggered by input?
#define ANSWER_NO 0 /* keywords don't match */
#define ANSWER_YES 1 /* keywords match */
#define ANSWER_DONE 2 /* already answered */
// input values (from the script)
struct in_val_s {
converse_value v; // data
uint8 d; // data-size or 0x00
};
// ONE data item from a converse script db
struct converse_db_s {
uint8 type; // 0=s 1=i
char *s;
uint8 i;
};
// frame around blocks of code that may or may not execute
struct convi_frame_s {
uint32 start;
converse_value start_c; // enter on c
bool run; // run(true) or skip(false) instructions
converse_value break_c; // will toggle run setting
};
vector<struct in_val_s> in; // control values (input/instruction)
uint32 in_start;
string text; // input text from script
vector<Std::string> rstrings; // string value(s) returned by op
string ystring; // modified by SETNAME, accessed with "$Y"
uint8 decl_v; // declared/initialized variable number
uint8 decl_t; // declared variable type: 0x00=none,0xb2=int,0xb3=string
Common::Stack<struct convi_frame_s *> *b_frame;
bool db_lvar;
converse_value db_loc;
converse_value db_offset;
const char *get_rstr(uint32 sn) const {
return ((sn < rstrings.size()) ? rstrings[sn].c_str() : "");
}
const string &get_ystr() const {
return ystring;
}
void set_ystr(const char *s);
void set_rstr(uint32 sn, const char *s);
converse_value add_rstr(const char *s);
void let(uint8 v, uint8 t) {
decl_v = v;
decl_t = t;
}
void let() {
decl_v = decl_t = 0x00;
}
void enter(converse_value c);
void leave();
void leave_all() {
while (b_frame && !b_frame->empty()) leave();
}
struct convi_frame_s *top_frame() const {
return ((b_frame && !b_frame->empty()) ? b_frame->top() : nullptr);
}
void do_frame(converse_value c);
void set_break(converse_value c) {
if (top_frame()) top_frame()->break_c = c;
}
converse_value get_break() const {
return (top_frame() ? top_frame()->break_c : 0x00);
}
void clear_break() {
set_break(0x00);
}
void set_run(bool r) {
if (top_frame()) top_frame()->run = r;
}
bool get_run() const {
return (top_frame() ? top_frame()->run : true);
}
public:
ConverseInterpret(Converse *owner);
virtual ~ConverseInterpret();
bool waiting() const {
return is_waiting;
}
void wait() {
is_waiting = true;
}
void unwait() {
is_waiting = false;
}
void stop() {
stopped = true;
wait();
}
bool end() {
return stopped;
}
void step();
protected:
/* collecting from script */
virtual void collect_input();
virtual struct in_val_s read_value();
void eval(uint32 vi = 0);
void add_val(converse_value c, uint8 d = 0);
void add_text(unsigned char c = 0);
/* manipulating collected input */
uint32 val_count() const {
return in.size();
}
converse_value get_val(uint32 vi);
uint8 get_val_size(uint32 vi);
converse_value pop_val();
uint8 pop_val_size();
const Std::string &get_text() const {
return text;
}
void flush() {
in.resize(0);
in_start = 0;
text.clear();
}
/* operating on input */
void exec();
void do_ctrl();
void do_text();
string get_formatted_text(const char *c_str);
converse_value pop_arg(Common::Stack<converse_typed_value> &vs);
converse_typed_value pop_typed_arg(Common::Stack<converse_typed_value> &vs);
virtual bool evop(Common::Stack<converse_typed_value> &i);
virtual bool op(Common::Stack<converse_typed_value> &i);
virtual bool op_create_new(Common::Stack<converse_typed_value> &i);
converse_value evop_eq(Common::Stack<converse_typed_value> &vs);
public:
virtual uint8 npc_num(uint32 n);//uint8 npc_num(uint32 n){return((n!=0xeb)?n:converse->npc_num);}
bool check_keywords(Std::string keystr, Std::string instr);
bool var_input() const {
return decl_t != 0x00;
}
void assign_input(); // set declared variable to Converse input
struct converse_db_s *get_db(uint32 loc, uint32 i);
converse_value get_db_integer(uint32 loc, uint32 i);
void set_db_integer(uint32 loc, uint32 i, converse_value val);
char *get_db_string(uint32 loc, uint32 i);
converse_value find_db_string(uint32 loc, const char *dstring);
/* value tests */
virtual bool is_print(converse_value check) const {
return (((check == 0x0a) || (check >= 0x20 && check <= 0x7a) || (check == 0x7e) || (check == 0x7b))); //added '~' 0x7e, '{' 0x7b for fm towns.
}
virtual bool is_ctrl(converse_value code) const {
return (((code >= 0xa1 || code == 0x9c || code == 0x9e) && !is_valop(code) && !is_datasize(code)));
}
virtual bool is_datasize(converse_value check) const {
return ((check == 0xd3 || check == 0xd2 || check == 0xd4));
}
virtual bool is_valop(converse_value check) const {
return (((check == 0x81) || (check == 0x82) || (check == 0x83)
|| (check == 0x84) || (check == 0x85) || (check == 0x86)
|| (check == 0x90) || (check == 0x91) || (check == 0x92)
|| (check == 0x93) || (check == 0x94) || (check == 0x95)
|| (check == 0x9a) || (check == 0x9b) || (check == 0x9d)
|| (check == 0x9f) || (check == 0xa0) || (check == 0xa7)
|| (check == 0xab) || (check == 0xb2) || (check == 0xb3)
|| (check == 0xb4) || (check == 0xb7) || (check == 0xbb) || (check == 0xc6)
|| (check == 0xc7) || (check == 0xca) || (check == 0xcc)
|| (check == 0xd7) || (check == 0xda) || (check == 0xdc)
|| (check == 0xdd) || (check == 0xe0) || (check == 0xe1)
|| (check == 0xe2) || (check == 0xe3) || (check == 0xe4)));
}
const char *evop_str(converse_value op);
const char *op_str(converse_value op);
};
class U6ConverseInterpret : public ConverseInterpret {
public:
U6ConverseInterpret(Converse *owner) : ConverseInterpret(owner) { }
// ~U6ConverseInterpret();
};
class WOUConverseInterpret : public ConverseInterpret {
public:
WOUConverseInterpret(Converse *owner) : ConverseInterpret(owner) { }
protected:
bool op_create_new(Common::Stack<converse_typed_value> &i) override;
};
class SETalkInterpret : public ConverseInterpret {
public:
SETalkInterpret(Converse *owner) : ConverseInterpret(owner) { }
};
class MDTalkInterpret : public WOUConverseInterpret {
public:
MDTalkInterpret(Converse *owner) : WOUConverseInterpret(owner) { }
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,217 @@
/* 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/core/game.h"
#include "ultima/nuvie/conf/configuration.h"
#include "ultima/nuvie/files/nuvie_io.h"
#include "ultima/nuvie/files/u6_lib_n.h"
#include "ultima/nuvie/files/u6_lzw.h"
#include "ultima/nuvie/core/converse_speech.h"
#include "ultima/nuvie/sound/sound_manager.h"
namespace Ultima {
namespace Nuvie {
ConverseSpeech::ConverseSpeech() : config(nullptr) {
}
/* Initialize global classes from the game.
*/
void ConverseSpeech::init(const Configuration *cfg) {
config = cfg;
}
ConverseSpeech::~ConverseSpeech() {
}
void ConverseSpeech::update() {
TownsSound sound;
SoundManager *sm = Game::get_game()->get_sound_manager();
if (!sm->is_audio_enabled() || !sm->is_speech_enabled())
return;
if (!list.empty()) {
if (sm->isSoundPLaying(handle) == false) {
list.pop_front();
if (!list.empty()) {
sound = list.front();
handle = sm->playTownsSound(sound.filename, sound.sample_num);
}
}
}
}
void ConverseSpeech::play_speech(uint16 actor_num, uint16 sample_num) {
Common::Path sample_file;
char filename[20]; // "/speech/charxxx.sam"
TownsSound sound;
SoundManager *sm = Game::get_game()->get_sound_manager();
if (!sm->is_audio_enabled() || !sm->is_speech_enabled())
return;
//translate the converse sample number into the CHAR number in the SPEECH directory if required.
if (actor_num == 202) //GUARDS
actor_num = 228;
if (actor_num == 201) //WISPS
actor_num = 229;
sample_num--;
Common::sprintf_s(filename, "speech%cchar%u.sam", U6PATH_DELIMITER, actor_num);
config->pathFromValue("config/townsdir", filename, sample_file);
DEBUG(0, LEVEL_DEBUGGING, "Loading Speech Sample %s:%d\n", sample_file.toString().c_str(), sample_num);
sound.filename = sample_file;
sound.sample_num = sample_num;
if (list.empty())
handle = sm->playTownsSound(sample_file, sample_num);
list.push_back(sound);
return;
}
NuvieIOBuffer *ConverseSpeech::load_speech(const Common::Path &filename, uint16 sample_num) {
unsigned char *compressed_data, *raw_audio, *wav_data;
sint16 *converted_audio;
uint32 decomp_size;
uint32 upsampled_size;
sint16 sample = 0, prev_sample;
U6Lib_n sam_file;
U6Lzw lzw;
NuvieIOBuffer *wav_buffer = 0;
uint32 j, k;
sam_file.open(filename, 4);
compressed_data = sam_file.get_item(sample_num, nullptr);
raw_audio = lzw.decompress_buffer(compressed_data, sam_file.get_item_size(sample_num), decomp_size);
free(compressed_data);
if (raw_audio != nullptr) {
wav_buffer = new NuvieIOBuffer();
upsampled_size = decomp_size + (int)floor((decomp_size - 1) / 4) * (2 + 2 + 2 + 1);
switch ((decomp_size - 1) % 4) {
case 1 :
upsampled_size += 2;
break;
case 2 :
upsampled_size += 4;
break;
case 3 :
upsampled_size += 6;
break;
}
DEBUG(0, LEVEL_DEBUGGING, "decomp_size %d, upsampled_size %d\n", decomp_size, upsampled_size);
wav_data = (unsigned char *)malloc(upsampled_size * sizeof(sint16) + 44); // 44 = size of wav header
wav_buffer->open(wav_data, upsampled_size * sizeof(sint16) + 44, false);
wav_init_header(wav_buffer, upsampled_size);
converted_audio = (sint16 *)&wav_data[44];
prev_sample = convert_sample(raw_audio[0]);
for (j = 1, k = 0; j < decomp_size; j++, k++) {
converted_audio[k] = prev_sample;
sample = convert_sample(raw_audio[j]);
switch (j % 4) { // calculate the in-between samples using linear interpolation.
case 0 :
case 1 :
case 2 :
converted_audio[k + 1] = (sint16)(0.666 * (float)prev_sample + 0.333 * (float)sample);
converted_audio[k + 2] = (sint16)(0.333 * (float)prev_sample + 0.666 * (float)sample);
k += 2;
break;
case 3 :
converted_audio[k + 1] = (sint16)(0.5 * (float)(prev_sample + sample));
k += 1;
break;
}
prev_sample = sample;
}
converted_audio[k] = sample;
}
free(raw_audio);
return wav_buffer;
}
inline sint16 ConverseSpeech::convert_sample(uint16 raw_sample) {
sint16 sample;
if (raw_sample & 128)
sample = ((sint16)(abs(128 - raw_sample) * 256) ^ 0xffff) + 1;
else
sample = raw_sample * 256;
// FIXME: Following code is for Big Endian sample conversion
// This was required for older libSDL audio output.
// May not be needed for ScummVM audio output?
#if 0
sint16 temp_sample = sample >> 8;
temp_sample |= (sample & 0xff) << 8;
sample = temp_sample;
#endif
return sample;
}
void ConverseSpeech::wav_init_header(NuvieIOBuffer *wav_buffer, uint32 audio_length) const {
wav_buffer->writeBuf((const unsigned char *)"RIFF", 4);
wav_buffer->write4(36 + audio_length * 2); //length of RIFF chunk
wav_buffer->writeBuf((const unsigned char *)"WAVE", 4);
wav_buffer->writeBuf((const unsigned char *)"fmt ", 4);
wav_buffer->write4(16); // length of format chunk
wav_buffer->write2(1); // PCM encoding
wav_buffer->write2(1); // mono
wav_buffer->write4(44100); // sample frequency 16KHz
wav_buffer->write4(44100 * 2); // sample rate
wav_buffer->write2(2); // BlockAlign
wav_buffer->write2(16); // Bits per sample
wav_buffer->writeBuf((const unsigned char *)"data", 4);
wav_buffer->write4(audio_length * 2); // length of data chunk
return;
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,63 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_CONVERSE_SPEECH_H
#define NUVIE_CORE_CONVERSE_SPEECH_H
#include "ultima/shared/std/containers.h"
#include "audio/mixer.h"
namespace Ultima {
namespace Nuvie {
class Configuration;
class U6Lib_n;
class U6Lzw;
class NuvieIOBuffer;
typedef struct TownsSound {
Common::Path filename;
uint16 sample_num;
} TownsSound;
class ConverseSpeech {
// game system objects from nuvie
const Configuration *config;
Audio::SoundHandle handle;
Std::list<TownsSound> list;
public:
ConverseSpeech();
~ConverseSpeech();
void init(const Configuration *cfg);
void update();
void play_speech(uint16 actor_num, uint16 sample_num);
protected:
NuvieIOBuffer *load_speech(const Common::Path &filename, uint16 sample_num);
inline sint16 convert_sample(uint16 raw_sample);
void wav_init_header(NuvieIOBuffer *wav_buffer, uint32 audio_length) const;
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,264 @@
/* 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/conf/configuration.h"
#include "ultima/nuvie/screen/screen.h"
#include "ultima/nuvie/files/nuvie_io.h"
#include "ultima/nuvie/files/nuvie_io_file.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/files/u6_lzw.h"
#include "ultima/nuvie/files/u6_shape.h"
#include "ultima/nuvie/files/u6_lib_n.h"
#include "ultima/nuvie/core/cursor.h"
namespace Ultima {
namespace Nuvie {
using Std::string;
using Std::vector;
Cursor::Cursor() : cursor_id(0), cur_x(-1), cur_y(-1), cleanup(nullptr),
hidden(false), screen(nullptr), config(nullptr), screen_w(0), screen_h(0) {
}
/* Returns true if mouse pointers file was loaded.
*/
bool Cursor::init(const Configuration *c, Screen *s, nuvie_game_t game_type) {
Std::string file;
Common::Path filename;
bool enable_cursors;
config = c;
screen = s;
screen_w = screen->get_width();
screen_h = screen->get_height();
config->value("config/general/enable_cursors", enable_cursors, true);
if (!enable_cursors)
return false;
switch (game_type) {
case NUVIE_GAME_U6 :
file = "u6mcga.ptr";
break;
case NUVIE_GAME_SE :
file = "secursor.ptr";
break;
case NUVIE_GAME_MD :
file = "mdcursor.ptr";
break;
}
config_get_path(config, file, filename);
if (filename != "")
if (load_all(filename, game_type) > 0)
return true;
return false;
}
/* Load pointers from `filename'. (lzw -> s_lib_32 -> shapes)
* Returns the number found in the file.
*/
uint32 Cursor::load_all(const Common::Path &filename, nuvie_game_t game_type) {
U6Lzw decompressor;
U6Lib_n pointer_list;
NuvieIOBuffer iobuf;
uint32 slib32_len = 0;
unsigned char *slib32_data;
if (game_type != NUVIE_GAME_U6) {
U6Lib_n file;
file.open(filename, 4, game_type);
slib32_data = file.get_item(0);
slib32_len = file.get_item_size(0);
} else {
slib32_data = decompressor.decompress_file(filename, slib32_len);
}
if (slib32_len == 0)
return 0;
// FIXME: u6lib_n assumes u6 libs have no filesize header
iobuf.open(slib32_data, slib32_len);
free(slib32_data);
if (!pointer_list.open(&iobuf, 4, NUVIE_GAME_MD))
return 0;
uint32 num_read = 0, num_total = pointer_list.get_num_items();
cursors.resize(num_total);
while (num_read < num_total) { // read each into a new MousePointer
MousePointer *ptr = nullptr;
U6Shape *shape = new U6Shape;
unsigned char *data = pointer_list.get_item(num_read);
if (!shape->load(data)) {
free(data);
delete shape;
break;
}
ptr = new MousePointer; // set from shape data
shape->get_hot_point(&(ptr->point_x), &(ptr->point_y));
shape->get_size(&(ptr->w), &(ptr->h));
ptr->shapedat = (unsigned char *)malloc(ptr->w * ptr->h);
memcpy(ptr->shapedat, shape->get_data(), ptr->w * ptr->h);
cursors[num_read++] = ptr;
free(data);
delete shape;
}
pointer_list.close();
iobuf.close();
return num_read;
}
/* Free data.
*/
void Cursor::unload_all() {
for (uint32 i = 0; i < cursors.size(); i++) {
if (cursors[i] && cursors[i]->shapedat)
free(cursors[i]->shapedat);
delete cursors[i];
}
if (cleanup)
free(cleanup);
}
/* Set active pointer.
*/
bool Cursor::set_pointer(uint8 ptr_num) {
if (ptr_num >= cursors.size() || !cursors[ptr_num])
return false;
cursor_id = ptr_num;
return true;
}
/* Draw self on screen at px,py, or at mouse location if px or py is -1.
* Returns false on failure.
*/
bool Cursor::display(int px, int py) {
if (cursors.empty() || !cursors[cursor_id])
return false;
if (hidden)
return true;
if (px == -1 || py == -1) {
screen->get_mouse_location(&px, &py);
// DEBUG(0,LEVEL_DEBUGGING,"mouse pos: %d,%d", px, py);
}
MousePointer *ptr = cursors[cursor_id];
fix_position(ptr, px, py); // modifies px, py
save_backing((uint32)px, (uint32)py, (uint32)ptr->w, (uint32)ptr->h);
screen->blit((uint16)px, (uint16)py, ptr->shapedat, 8, ptr->w, ptr->h, ptr->w, true);
// screen->update(px, py, ptr->w, ptr->h);
add_update(px, py, ptr->w, ptr->h);
update();
return true;
}
/* Restore backing behind cursor (hide until next display). Must call update()
* sometime after to remove from screen.
*/
void Cursor::clear() {
if (cleanup) {
screen->restore_area(cleanup, &cleanup_area);
cleanup = nullptr;
// screen->update(cleanup_area.left, cleanup_area.top, cleanup_area.w, cleanup_area.h);
add_update(cleanup_area.left, cleanup_area.top, cleanup_area.width(), cleanup_area.height());
}
}
/* Offset requested position px,py by pointer hotspot, and screen boundary.
*/
inline void Cursor::fix_position(MousePointer *ptr, int &px, int &py) {
if ((px - ptr->point_x) < 0) // offset by hotspot
px = 0;
else
px -= ptr->point_x;
if ((py - ptr->point_y) < 0)
py = 0;
else
py -= ptr->point_y;
if ((px + ptr->w) >= screen_w) // don't draw offscreen
px = screen_w - ptr->w - 1;
if ((py + ptr->h) >= screen_h)
py = screen_h - ptr->h - 1;
}
/* Copy cleanup area (cursor backingstore) from screen.
*/
void Cursor::save_backing(uint32 px, uint32 py, uint32 w, uint32 h) {
if (cleanup) {
free(cleanup);
cleanup = nullptr;
}
cleanup_area.left = px; // cursor must be drawn LAST for this to work
cleanup_area.top = py;
cleanup_area.setWidth(w);
cleanup_area.setHeight(h);
cleanup = screen->copy_area(&cleanup_area);
}
/* Mark update_area (cleared/displayed) as updated on the screen.
*/
void Cursor::update() {
screen->update(update_area.left, update_area.top, update_area.width(), update_area.height());
update_area = Common::Rect();
}
/* Add to update_area.
*/
void Cursor::add_update(uint16 x, uint16 y, uint16 w, uint16 h) {
if (update_area.width() == 0 || update_area.height() == 0) {
update_area.left = x;
update_area.top = y;
update_area.setWidth(w);
update_area.setHeight(h);
} else {
uint16 x2 = x + w, y2 = y + h,
update_x2 = update_area.right, update_y2 = update_area.bottom;
if (x <= update_area.left) update_area.left = x;
if (y <= update_area.top) update_area.top = y;
if (x2 >= update_x2) update_x2 = x2;
if (y2 >= update_y2) update_y2 = y2;
update_area.setWidth(update_x2 - update_area.left);
update_area.setHeight(update_y2 - update_area.top);
}
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,112 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_CURSOR_H
#define NUVIE_CORE_CURSOR_H
#include "ultima/shared/std/string.h"
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Nuvie {
class Configuration;
class Screen;
class U6Shape;
typedef struct {
uint16 point_x, point_y; // hotspot
unsigned char *shapedat;
uint16 w, h;
} MousePointer;
/* Contains all mouse pointers, with hotspot and draw methods that work on the
* active cursor.
*/
class Cursor {
friend class Screen;
Screen *screen;
const Configuration *config;
sint32 cur_x, cur_y; // location on screen, unused normally
Std::vector<MousePointer *> cursors; // pointer list
uint8 cursor_id; // which pointer is active
unsigned char *cleanup; // restore image behind cursor
Common::Rect cleanup_area;
Common::Rect update_area; // clear & display are updated at once (avoid flicker)
bool hidden;
uint16 screen_w, screen_h;
void add_update(uint16 x, uint16 y, uint16 w, uint16 h);
inline void fix_position(MousePointer *ptr, int &px, int &py);
void save_backing(uint32 px, uint32 py, uint32 w, uint32 h);
public:
Cursor();
~Cursor() {
unload_all();
}
bool init(const Configuration *c, Screen *s, nuvie_game_t game_type);
uint32 load_all(const Common::Path &filename, nuvie_game_t game_type);
void unload_all();
bool set_pointer(uint8 ptr_num);
void reset_position() {
cur_x = -1;
cur_y = -1;
}
void move(uint32 px, uint32 py) {
cur_x = px;
cur_y = py;
}
void hide() {
hidden = true;
clear();
update();
}
void show() {
hidden = false;
}
void get_hotspot(uint16 &x, uint16 &y) const {
x = cursors[cursor_id]->point_x;
y = cursors[cursor_id]->point_y;
}
bool display() {
return display(cur_x, cur_y);
}
bool display(int px, int py);
void clear();
void update();
bool is_visible() const {
return !hidden;
}
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,31 @@
/* 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 "common/str.h"
#include "common/textconsole.h"
namespace Ultima {
namespace Nuvie {
} // End of namespace Nuvie
} // End of namespace Ultima

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,703 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_EFFECT_H
#define NUVIE_CORE_EFFECT_H
#include "ultima/nuvie/misc/call_back.h"
#include "ultima/nuvie/core/map.h"
#include "ultima/nuvie/core/obj_manager.h"
#include "ultima/nuvie/core/anim_manager.h"
namespace Ultima {
namespace Nuvie {
//class Actor;
class EffectManager;
class Game;
class MapWindow;
class NuvieAnim;
class Screen;
class TimedAdvance;
class TimedCallback;
class ObjManager;
// Effects add themselves to EffectManager and most start immediately.
/* Effects: * = unwritten or untested
* Quake - earthquake from cyclops or volcanos
* Hit - hit actor anim + sfx
* Explosive - explosion caused by powder keg, volcanos, or cannonball hit
* ThrowObject - any thrown object or tile
* Cannonball (FIX: change to UseCodeThrow)
* Missile - throw object to ground or actor; optionally cause damage
* *Boomerang - spin Missile and return to sender
* Drop - throw obj from inventory to ground
* Sleep - pause game & advance time quickly
* Fade - fade the mapwindow in or out
* GameFadeIn - blocks user-input until Fade is complete
* *Palette - do something with the color palette
* Vanish - fade from an image of the mapwindow to the real mapwindow
* *FadeObject - might not need this since Vanish can be used
* U6WhitePotion - will probably make PaletteEffect to do this
*/
/* Control animation and sounds in the game world.
*/
class Effect : public CallBack {
protected:
Game *game;
EffectManager *effect_manager;
bool defunct;
uint32 retain_count;
public:
Effect();
~Effect() override;
void retain() {
retain_count++;
}
void release() {
if (retain_count > 0) retain_count--;
}
bool is_retained() const {
return retain_count == 0 ? false : true;
}
void delete_self() {
defunct = true;
}
void add_anim(NuvieAnim *anim);
bool is_defunct() const {
return defunct;
}
uint16 callback(uint16, CallBack *, void *) override {
return 0;
}
};
/* Toss a cannon ball from one actor to another, or from an object towards
* a numbered direction.
*/
class CannonballEffect : public Effect {
UseCode *usecode;
NuvieAnim *anim;
// *sfx;
Obj *obj;
MapCoord target_loc; // where cannonball will hit
void start_anim();
public:
CannonballEffect(Obj *src_obj, sint8 direction = -1);
// CannonballEffect(Actor *src_actor, Actor *target_actor); from a ship
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
};
class ProjectileEffect : public Effect {
protected:
uint16 tile_num;
MapCoord start_loc; // where explosion will start
vector<MapCoord> targets;
uint8 anim_speed;
bool trail;
uint16 initial_tile_rotation;
uint16 rotation_amount;
uint8 src_tile_y_offset;
uint16 finished_tiles;
vector<MapEntity> hit_entities;
virtual void start_anim();
public:
ProjectileEffect() : tile_num(0), anim_speed(0), trail(false),
initial_tile_rotation(0), rotation_amount(0), src_tile_y_offset(0),
finished_tiles(0) {
}
ProjectileEffect(uint16 tileNum, MapCoord start, MapCoord target, uint8 speed, bool trailFlag, uint16 initialTileRotation, uint16 rotationAmount, uint8 src_y_offset);
ProjectileEffect(uint16 tileNum, MapCoord start, const vector<MapCoord> &t, uint8 speed, bool trailFlag, uint16 initialTileRotation);
void init(uint16 tileNum, MapCoord start, const vector<MapCoord> &t, uint8 speed, bool trailFlag, uint16 initialTileRotation, uint16 rotationAmount, uint8 src_y_offset);
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
vector<MapEntity> *get_hit_entities() {
return &hit_entities;
}
};
class ExpEffect : public ProjectileEffect {
//UseCode *usecode;
NuvieAnim *anim;
//Obj *obj;
uint16 exp_tile_num;
protected:
void start_anim() override;
public:
ExpEffect(uint16 tileNum, const MapCoord &location);
};
/* Use to add an effect with timed activity. Self-contained timer must be
* stopped/started with the included methods.
*/
class TimedEffect : public Effect {
protected:
TimedCallback *timer;
public:
TimedEffect() {
timer = nullptr;
}
TimedEffect(uint32 delay) {
timer = nullptr;
start_timer(delay);
}
~TimedEffect() override {
stop_timer();
}
void start_timer(uint32 delay);
void stop_timer();
void delete_self() {
stop_timer();
Effect::delete_self();
}
uint16 callback(uint16 msg, CallBack *caller, void *data) override {
if (msg == MESG_TIMED) delete_self(); //= 0;
return 0;
}
};
/* Shake the visible play area around.
*/
class QuakeEffect : public TimedEffect {
MapWindow *map_window;
static QuakeEffect *current_quake; // do nothing if already active
sint32 sx, sy; // last map_window movement amount
MapCoord orig; // map_window location at start
Actor *orig_actor; // center map_window on actor
uint32 stop_time;
uint8 strength; // magnitude
public:
QuakeEffect(uint8 magnitude, uint32 duration, Actor *keep_on = nullptr);
~QuakeEffect() override;
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
void init_directions();
void recenter_map();
void stop_quake();
};
/* Hit target actor.
*/
class HitEffect : public Effect {
public:
HitEffect(Actor *target, uint32 duration = 300);
HitEffect(const MapCoord &location);
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
};
/* Print text to MapWindow for a given duration
*/
class TextEffect : public Effect {
public:
TextEffect(Std::string text);
TextEffect(Std::string text, const MapCoord &location);
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
};
/* Create explosion animation and sounds from the source location out to
* specified radius. Hit actors and objects for `dmg'.
*/
class ExplosiveEffect : public Effect {
protected:
NuvieAnim *anim;
// *sfx;
MapCoord start_at;
uint32 radius;
uint16 hit_damage; // hp taken off actors hit by explosion
void start_anim();
public:
ExplosiveEffect(uint16 x, uint16 y, uint32 size, uint16 dmg = 0);
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
// children can override
virtual void delete_self() {
Effect::delete_self();
}
virtual bool hit_object(Obj *obj) {
return false; // explosion hit something
}
// true return=end effect
};
/* Explosion that sends usecode event to an object on completion.
*/
class UseCodeExplosiveEffect : public ExplosiveEffect {
Obj *obj; // explosion came from this object (can be nullptr)
Obj *original_obj; // don't hit this object (chain-reaction avoidance hack)
public:
UseCodeExplosiveEffect(Obj *src_obj, uint16 x, uint16 y, uint32 size, uint16 dmg = 0, Obj *dont_hit_me = nullptr)
: ExplosiveEffect(x, y, size, dmg), obj(src_obj), original_obj(dont_hit_me) {
}
void delete_self() override;
bool hit_object(Obj *hit_obj) override; // explosion hit something
};
/* Toss object tile from one location to another with a TossAnim, and play a
* sound effect. The ThrowObjectEffect is constructed with uninitialized
* parameters and isn't started until start_anim() is called.
*/
class ThrowObjectEffect : public Effect {
protected:
ObjManager *obj_manager;
NuvieAnim *anim; // TossAnim
// *sfx;
MapCoord start_at, stop_at; // start_at -> stop_at
Obj *throw_obj; // object being thrown
const Tile *throw_tile; // graphic to use (default is object's tile)
uint16 throw_speed; // used in animation
uint16 degrees; // rotation of tile
uint8 stop_flags; // TossAnim blocking flags
public:
ThrowObjectEffect();
~ThrowObjectEffect() override { }
void hit_target(); // stops effect
void start_anim();
uint16 callback(uint16 msg, CallBack *caller, void *data) override = 0;
};
/* Drop an object from an actor's inventory. Object is removed from the actor
* after starting the effect, and added to the map when the effect is complete.
* Effect speed is gametype-defined.
*/
class DropEffect : public ThrowObjectEffect {
Actor *drop_from_actor;
public:
DropEffect(Obj *obj, uint16 qty, Actor *actor, MapCoord *drop_loc);
void hit_target();
void get_obj(Obj *obj, uint16 qty);
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
};
#define MISSILE_DEFAULT_SPEED 200
#define MISSILE_HIT_TARGET TOSS_TO_BLOCKING
#define MISSILE_HIT_OBJECTS (TOSS_TO_BLOCKING|TOSS_TO_OBJECT)
#define MISSILE_HIT_ACTORS (TOSS_TO_BLOCKING|TOSS_TO_ACTOR)
#define MISSILE_HIT_ALL (TOSS_TO_BLOCKING|TOSS_TO_OBJECT|TOSS_TO_ACTOR)
/* Throw a missile towards a target location. If the target is an actor or
* object, it will be hit for the requested damage. If the target is an empty
* map location, the object will be added to the map. The missile always stops
* if hitting a blocking tile.
*
* Decide in the attack logic, before constructing this, whether or not it was
* successful, and use the appropriate constructor. You can set the effect to
* hit any actors or objects in the way if the attack missed.
*/
class MissileEffect : public ThrowObjectEffect {
ActorManager *actor_manager;
uint16 hit_damage; // hp taken off actor/object hit by missile
Actor *hit_actor;
Obj *hit_obj;
public:
MissileEffect(uint16 tile_num, uint16 obj_n, const MapCoord &source,
const MapCoord &target, uint8 dmg, uint8 intercept = MISSILE_HIT_TARGET, uint16 speed = MISSILE_DEFAULT_SPEED);
void init(uint16 tile_num, uint16 obj_n, const MapCoord &source,
const MapCoord &target, uint32 dmg, uint8 intercept, uint32 speed);
void hit_target();
void hit_blocking();
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
};
#if 0
/* Throw an object and bring it back.
*/
class BoomerangEffect : public ThrowObjectEffect {
// I might even add an arc from the center line for a cool effect.
};
/* Cycle or modify the game palette in some way.
*/
class PaletteEffect : public TimedEffect {
// palette effects are created from child classes (new BlackPotionEffect();)
// ...and these can include SFX like any other effect
// but PaletteEffect is not abstract (new PaletteEffect(timing & color params...);)
};
#endif
/* For sleeping at inns. Fade-out, advance time, and fade-in.
*/
class SleepEffect : public Effect {
TimedAdvance *timer; // timed event
uint8 stop_hour, stop_minute; // sleep until this time
Std::string stop_time;
public:
SleepEffect(Std::string until);
SleepEffect(uint8 to_hour);
~SleepEffect() override;
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
void delete_self();
};
typedef enum { FADE_PIXELATED, FADE_CIRCLE, FADE_PIXELATED_ONTOP } FadeType;
typedef enum { FADE_IN, FADE_OUT } FadeDirection;
/* Manipulate the MapWindow for two types of fades. One is a stippled-like fade
* that draws pixels to random locations on the screen until completely flooded
* with a set color. The other changes the ambient light until fully black.
*/
class FadeEffect : public TimedEffect {
protected:
static FadeEffect *current_fade; // do nothing if already active
MapWindow *map_window;
Screen *screen; // for PIXELATED, the overlay is blitted to the screen...
Common::Rect *viewport; // ...at the MapWindow coordinates set here
Graphics::ManagedSurface *overlay; // this is what gets blitted
FadeType fade_type; // PIXELATED[_ONTOP] or CIRCLE
FadeDirection fade_dir; // IN (removing color) or OUT (adding color)
uint32 fade_speed; // meaning of this depends on fade_type
uint8 pixelated_color; // color from palette that is being faded to/from
Graphics::ManagedSurface *fade_from; // image being faded from or to (or nullptr if coloring)
uint16 fade_x, fade_y; // start fade from this point (to fade_from size)
uint32 evtime, prev_evtime; // time of last message to callback()
uint32 pixel_count, colored_total; // number of pixels total/colored
uint16 fade_iterations; // number of times we've updated the fade effect
public:
FadeEffect(FadeType fade, FadeDirection dir, uint32 color = 0, uint32 speed = 0);
FadeEffect(FadeType fade, FadeDirection dir, Graphics::ManagedSurface *capture, uint32 speed = 0);
FadeEffect(FadeType fade, FadeDirection dir, Graphics::ManagedSurface *capture, uint16 x, uint16 y, uint32 speed = 0);
~FadeEffect() override;
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
bool pixelated_fade_out();
bool pixelated_fade_in();
bool circle_fade_out();
bool circle_fade_in();
void delete_self();
protected:
void init(FadeType fade, FadeDirection dir, uint32 color, Graphics::ManagedSurface *capture, uint16 x, uint16 y, uint32 speed);
void init_pixelated_fade();
void init_circle_fade();
inline bool find_free_pixel(uint32 &rnum, uint32 pixel_count);
uint32 pixels_to_check();
bool pixelated_fade_core(uint32 pixels_to_check, sint16 fade_to);
// inline uint32 get_random_pixel(uint16 center_thresh = 0);
};
/* Front-end to FadeEffect that fades in, and resumes game.
*/
class GameFadeInEffect : public FadeEffect {
public:
GameFadeInEffect(uint32 color);
~GameFadeInEffect() override;
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
};
/* Captures an image of the MapWindow without an object, then places the object
* on the map and fades to the new image. (or the opposite if FADE_OUT is used)
*/
class FadeObjectEffect : public Effect {
ObjManager *obj_manager;
Obj *fade_obj;
FadeDirection fade_dir;
public:
FadeObjectEffect(Obj *obj, FadeDirection dir);
~FadeObjectEffect() override;
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
};
/* Do a blocking fade-to (FADE_OUT) from a captured image of the game area, to
* the active game area. (transparent) This is used for vanish or morph effects.
*/
#define VANISH_WAIT true
#define VANISH_NOWAIT false
class VanishEffect : public Effect {
bool input_blocked;
public:
VanishEffect(bool pause_user = VANISH_NOWAIT);
~VanishEffect() override;
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
};
class TileFadeEffect : public TimedEffect {
//TileAnim *anim;
//Tile *to_tile;
//Tile *anim_tile;
Actor *actor;
//uint8 color_from, color_to;
bool inc_reverse;
uint16 spd;
uint16 num_anim_running;
public:
TileFadeEffect(const MapCoord &loc, Tile *from, Tile *to, FadeType type, uint16 speed);
//TileFadeEffect(MapCoord loc, Tile *from, uint8 color_from, uint8 color_to, bool reverse, uint16 speed);
TileFadeEffect(Actor *a, uint16 speed);
~TileFadeEffect() override;
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
protected:
void add_actor_anim();
void add_fade_anim(const MapCoord &loc, Tile *tile);
void add_tile_anim(const MapCoord &loc, Tile *tile);
void add_obj_anim(Obj *obj);
};
class TileBlackFadeEffect : public TimedEffect {
Actor *actor;
Obj *obj;
uint8 color;
bool reverse;
uint16 fade_speed;
uint16 num_anim_running;
public:
TileBlackFadeEffect(Actor *a, uint8 fade_color, uint16 speed);
TileBlackFadeEffect(Obj *o, uint8 fade_color, uint16 speed);
~TileBlackFadeEffect() override;
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
protected:
void init(uint8 fade_color, uint16 speed);
void add_actor_anim();
void add_obj_anim(Obj *o);
void add_tile_anim(const MapCoord &loc, Tile *tile);
};
/* Briefly modify the mapwindow colors, disable map-blacking and player
* movement for a few seconds, then enable both.
*/
class XorEffect : public TimedEffect {
MapWindow *map_window;
uint32 length;
Graphics::ManagedSurface *capture; // this is what gets blitted
void xor_capture(uint8 mod);
void init_effect();
public:
/* eff_ms=length of visual effect */
XorEffect(uint32 eff_ms);
~XorEffect() override { }
/* Called by the timer between each effect stage. */
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
};
/* Briefly modify the mapwindow colors, disable map-blacking and player
* movement for a few seconds, then enable both.
*/
class U6WhitePotionEffect : public TimedEffect {
MapWindow *map_window;
uint8 state; // 0=start, 1=eff1, 2=eff2, 3=x-ray, 4=complete
uint32 start_length, eff1_length, eff2_length, xray_length;
Graphics::ManagedSurface *capture; // this is what gets blitted
Obj *potion; // allows effect to call usecode and delete object
void xor_capture(uint8 mod);
void init_effect();
public:
/* eff_ms=length of visual effect; delay_ms=length of x-ray effect */
U6WhitePotionEffect(uint32 eff_ms, uint32 delay_ms, Obj *callback_obj = nullptr);
~U6WhitePotionEffect() override { }
/* Called by the timer between each effect stage. */
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
};
class XRayEffect : public TimedEffect {
uint32 xray_length;
void init_effect();
public:
/* eff_ms=length of x-ray effect */
XRayEffect(uint32 eff_ms);
~XRayEffect() override { }
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
};
/* Pause the game, create an effect, and wait for user input to continue. */
class PauseEffect: public Effect {
public:
/* Called by the Effect handler when input is available. */
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
virtual void delete_self() {
Effect::delete_self();
}
PauseEffect();
~PauseEffect() override { }
};
/* Gather text from scroll input then continue. */
class TextInputEffect: public Effect {
Std::string input;
public:
/* Called by the Effect handler when input is available. */
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
TextInputEffect(const char *allowed_chars, bool can_escape);
~TextInputEffect() override { }
Std::string get_input() {
return input;
}
};
class WizardEyeEffect: public Effect {
public:
/* Called by the Effect handler when input is available. */
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
virtual void delete_self() {
Effect::delete_self();
}
WizardEyeEffect(const MapCoord &location, uint16 duration);
~WizardEyeEffect() override { }
};
/* colors for PeerEffect */
const uint8 peer_tilemap[4] = {
0x0A, // GROUND/PASSABLE
0x09, // WATER
0x07, // WALLS/BLOCKED
0x0C // DANGER/DAMAGING
};
#define PEER_TILEW 4
const uint8 peer_tile[PEER_TILEW * PEER_TILEW] = {
0, 1, 0, 1,
1, 0, 1, 0,
0, 1, 0, 1,
1, 0, 1, 0
};
/* Display an overview of the current area in the MapWindow. Any new actions
* cancel the effect and return to the prompt.
* (area is 48x48 tiles around the player, regardless of MapWindow size)
*/
class PeerEffect : public PauseEffect {
MapWindow *map_window;
Graphics::ManagedSurface *overlay; // this is what gets blitted
Obj *gem; // allows effect to call usecode and delete object
MapCoord area; // area to display (top-left corner)
uint8 tile_trans; // peer_tile transparency mask (0 or 1)
uint16 map_pitch;
inline void blit_tile(uint16 x, uint16 y, uint8 c);
inline void blit_actor(Actor *actor);
inline uint8 get_tilemap_type(uint16 wx, uint16 wy, uint8 wz);
void fill_buffer(uint8 *mapbuffer, uint16 x, uint16 y);
void peer();
public:
PeerEffect(uint16 x, uint16 y, uint8 z, Obj *callback_obj = 0);
~PeerEffect() override { }
void init_effect();
void delete_self() override;
};
class WingStrikeEffect : public Effect {
protected:
Actor *actor;
public:
WingStrikeEffect(Actor *target_actor);
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
};
class HailStormEffect : public Effect {
public:
HailStormEffect(const MapCoord &target);
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
};
#define EFFECT_PROCESS_GUI_INPUT true
/* Run an effect asynchronously and keep updating the world until the effect completes. */
class AsyncEffect : public Effect {
protected:
Effect *effect;
bool effect_complete;
public:
AsyncEffect(Effect *e);
~AsyncEffect() override;
void run(bool process_gui_input = false);
uint16 callback(uint16 msg, CallBack *caller, void *data) override;
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,135 @@
/* 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/core/map.h"
#include "ultima/nuvie/core/timed_event.h"
#include "ultima/nuvie/core/effect.h"
#include "ultima/nuvie/core/effect_manager.h"
namespace Ultima {
namespace Nuvie {
EffectManager::EffectManager() {
}
EffectManager::~EffectManager() {
while (!effects.empty())
delete_effect(effects.front());
}
/* Delete an effect and remove it from the list.
*/
void EffectManager::delete_effect(Effect *eff) {
EffectIterator ei = effects.begin();
while (ei != effects.end()) {
if (*ei == eff) {
signal_watch(eff);
delete eff;
effects.erase(ei);
return;
}
++ei;
}
}
/* Add an (already existent) effect to the list.
*/
void EffectManager::add_effect(Effect *eff) {
effects.push_back(eff);
}
/* Delete completed effects.
*/
void EffectManager::update_effects() {
EffectIterator ei = effects.begin();
while (ei != effects.end()) {
if ((*ei)->is_defunct()/* && !has_message(*ei)*/) {
signal_watch(*ei);
if ((*ei)->is_retained() == false) { //if no longer needed by other objects we can delete.
delete(*ei);
ei = effects.erase(ei);
} else ++ei;
} else ++ei;
}
}
/* Returns true if there are any effects still active.
*/
bool EffectManager::has_effects() const {
if (!effects.empty()) {
ConstEffectIterator i = effects.begin();
while (i != effects.end())
if (!(*i)->is_defunct()) // effect is still active
return true;
}
return false; // no effects, or all effects are complete
}
/* Add a watched effect. This will send effect completion message to the
* target when the effect is deleted.
*/
void EffectManager::watch_effect(CallBack *callback_target, Effect *watch) {
EffectWatch new_watch;
new_watch.watcher = callback_target;
new_watch.effect = watch;
watched.push_back(new_watch);
}
/* Remove a watched effect, or all watched effects for target.
*/
void EffectManager::unwatch_effect(CallBack *callback_target, Effect *watch) {
if (!watched.empty()) {
WatchIterator i = watched.begin();
while (i != watched.end())
if ((*i).watcher == callback_target
&& ((*i).effect == watch || watch == nullptr)) {
i = watched.erase(i); // resume from next element
} else ++i;
}
}
/* Signal effect completion if it is being watched, and stop watching it.
*/
void EffectManager::signal_watch(Effect *effect) {
EffectWatch *watch = find_effect_watch(effect);
if (watch) {
if (watch->watcher)
watch->watcher->callback(EFFECT_CB_COMPLETE, nullptr, effect);
unwatch_effect(watch->watcher, effect);
}
}
/* Returns watch for an effect. (or nullptr)
*/
EffectManager::EffectWatch *EffectManager::find_effect_watch(Effect *effect) {
if (!watched.empty()) {
WatchIterator i = watched.begin();
while (i != watched.end())
if ((*i).effect == effect)
return (&(*i));
else ++i;
}
return nullptr;
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,68 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_EFFECT_MANAGER_H
#define NUVIE_CORE_EFFECT_MANAGER_H
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Nuvie {
class Effect;
/* This just keeps a list of Effect pointers, and deletes them when requested.
*/
class EffectManager {
friend class Effect;
typedef Std::vector<Effect *>::iterator EffectIterator;
typedef Std::vector<Effect *>::const_iterator ConstEffectIterator;
/* For each EffectWatch, a message will be sent to "watcher" when
"effect" is deleted. */
typedef struct {
CallBack *watcher;
Effect *effect;
} EffectWatch;
typedef Std::vector<EffectWatch>::iterator WatchIterator;
Std::vector<Effect *> effects; // the simple list
Std::vector<EffectWatch> watched;
void add_effect(Effect *eff); // only effects can add themselves
void signal_watch(Effect *effect);
EffectWatch *find_effect_watch(Effect *effect);
public:
EffectManager();
~EffectManager();
void delete_effect(Effect *eff); // anyone may delete an effect
void update_effects(); // check and delete
bool has_effects() const;
void watch_effect(CallBack *callback_target, Effect *watch);
void unwatch_effect(CallBack *callback_target, Effect *watch = nullptr);
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,240 @@
/* 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/conf/configuration.h"
#include "ultima/nuvie/actors/actor.h"
#include "ultima/nuvie/core/tile_manager.h"
#include "ultima/nuvie/actors/actor_manager.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/misc/u6_llist.h"
#include "ultima/nuvie/core/egg_manager.h"
#include "ultima/nuvie/files/nuvie_io_file.h"
#include "ultima/nuvie/core/game_clock.h"
#include "ultima/nuvie/core/game.h"
#include "ultima/nuvie/core/party.h"
#include "ultima/nuvie/actors/u6_work_types.h"
#include "ultima/nuvie/core/u6_objects.h" //needed for silver serpent exception
namespace Ultima {
namespace Nuvie {
/* ALWAYS means the time is unset, is unknown, or day and night are both set */
typedef enum {
EGG_HATCH_ALWAYS, EGG_HATCH_DAY, EGG_HATCH_NIGHT
} egg_hatch_time;
static const int EGG_DAY_HOUR = 06; /* first hour of the day */
static const int EGG_NIGHT_HOUR = 19; /* first hour of night */
static inline egg_hatch_time get_egg_hatch_time(uint8 EQ) {
return (EQ < 10) ? EGG_HATCH_ALWAYS
: (EQ < 20) ? EGG_HATCH_DAY
: (EQ < 30) ? EGG_HATCH_NIGHT : EGG_HATCH_ALWAYS;
}
EggManager::EggManager(nuvie_game_t type)
: gametype(type), actor_manager(nullptr), obj_manager(nullptr),
not_spawning_actors(false) {
}
EggManager::~EggManager() {
}
void EggManager::clean(bool keep_obj) {
Std::list<Egg *>::iterator egg_iter;
for (egg_iter = egg_list.begin(); egg_iter != egg_list.end();) {
//egg = *egg_iter;
// eggs are always on the map now.
// if(keep_obj == false)
// delete_obj(egg->obj);
delete *egg_iter;
egg_iter = egg_list.erase(egg_iter);
}
}
void EggManager::add_egg(Obj *egg_obj) {
Egg *egg;
if (egg_obj == nullptr)
return;
egg = new Egg();
egg->obj = egg_obj;
egg_list.push_back(egg);
return;
}
void EggManager::remove_egg(Obj *egg_obj, bool keep_obj) {
Std::list<Egg *>::iterator egg_iter;
for (egg_iter = egg_list.begin(); egg_iter != egg_list.end(); egg_iter++) {
if ((*egg_iter)->obj == egg_obj) {
//if(keep_obj == false) eggs always on map now.
//obj_manager->unlink_from_engine((*egg_iter)->obj);
//delete_obj((*egg_iter)->obj);
delete *egg_iter;
egg_list.erase(egg_iter);
break;
}
}
return;
}
void EggManager::set_egg_visibility(bool show_eggs) {
for (Egg *egg : egg_list)
egg->obj->set_invisible(!show_eggs);
}
void EggManager::spawn_eggs(uint16 x, uint16 y, uint8 z, bool teleport) {
for (Egg *egg : egg_list) {
uint8 quality = egg->obj->quality;
sint16 dist_x = abs((sint16)egg->obj->x - x);
sint16 dist_y = abs((sint16)egg->obj->y - y);
//Deactivate eggs that are more than 20 tiles from player.
if ((egg->obj->status & OBJ_STATUS_EGG_ACTIVE) && (egg->obj->z != z || (dist_x >= 20 || dist_y >= 20))) {
egg->obj->status &= (0xff ^ OBJ_STATUS_EGG_ACTIVE);
DEBUG(0, LEVEL_DEBUGGING, "Reactivate egg at (%x,%x,%d)\n", egg->obj->x, egg->obj->y, egg->obj->z);
}
if (dist_x < 20 && dist_y < 20 && egg->obj->z == z
&& (dist_x > 8 || dist_y > 8 || !Game::get_game()->is_orig_style() || teleport)) {
if ((egg->obj->status & OBJ_STATUS_EGG_ACTIVE) == 0) {
egg->obj->status |= OBJ_STATUS_EGG_ACTIVE;
uint8 hatch_probability = (NUVIE_RAND() % 100) + 1;
DEBUG(0, LEVEL_DEBUGGING, "Checking Egg (%x,%x,%x). Rand: %d Probability: %d%%", egg->obj->x, egg->obj->y, egg->obj->z, hatch_probability, egg->obj->qty);
DEBUG(1, LEVEL_DEBUGGING, " Align: %s", get_actor_alignment_str(static_cast<ActorAlignment>(quality % 10)));
if (quality < 10) DEBUG(1, LEVEL_DEBUGGING, " (always)"); // 0-9
else if (quality < 20) DEBUG(1, LEVEL_DEBUGGING, " (day)"); // 10-19
else if (quality < 30) DEBUG(1, LEVEL_DEBUGGING, " (night)"); // 20-29
else if (quality < 40) DEBUG(1, LEVEL_DEBUGGING, " (day+night)"); // 30-39
DEBUG(1, LEVEL_DEBUGGING, "\n");
spawn_egg(egg->obj, hatch_probability);
}
}
}
return;
}
bool EggManager::spawn_egg(Obj *egg, uint8 hatch_probability) {
U6Link *link;
uint16 i;
Obj *obj, *spawned_obj;
uint16 qty;
uint8 hour = Game::get_game()->get_clock()->get_hour();
ActorAlignment alignment = static_cast<ActorAlignment>(egg->quality % 10);
// check time that the egg will hach
egg_hatch_time period = get_egg_hatch_time(egg->quality);
if (period == EGG_HATCH_ALWAYS
|| (period == EGG_HATCH_DAY && hour >= EGG_DAY_HOUR && hour < EGG_NIGHT_HOUR)
|| (period == EGG_HATCH_NIGHT && !(hour >= EGG_DAY_HOUR && hour < EGG_NIGHT_HOUR))) {
if (egg->container == nullptr) {
DEBUG(1, LEVEL_WARNING, " egg at (%x,%x,%x) does not contain any embryos!", egg->x, egg->y, egg->z);
}
// check random probability that the egg will hatch
if ((egg->qty == 100 || hatch_probability <= egg->qty) && egg->container) { // Hatch the egg.
assert(egg->container);
for (link = egg->container->start(); link != nullptr; link = link->next) {
obj = (Obj *)link->data;
qty = obj->qty;
if (gametype == NUVIE_GAME_U6 && obj->obj_n == OBJ_U6_SILVER_SERPENT) //U6 silver serpents only hatch once per egg.
qty = 1;
else if (egg->qty != 100) // egg qty 100: do not randomize spawn count
qty = (NUVIE_RAND() % qty) + 1; // try to spawn 1 to qty entities
for (i = 0; i < qty; i++) {
if ((gametype == NUVIE_GAME_U6 && (obj->obj_n >= OBJ_U6_GIANT_RAT || obj->obj_n == OBJ_U6_CHEST))
|| obj->quality != 0) { /* spawn temp actor we know it's an actor if it has a non-zero worktype. */
if ((not_spawning_actors && Game::get_game()->are_cheats_enabled())
|| Game::get_game()->is_armageddon())
break;
// group new actors randomly if egg space already occupied
Actor *prev_actor = actor_manager->get_actor(egg->x, egg->y, egg->z);
Actor *new_actor = nullptr;
MapCoord actor_loc = MapCoord(egg->x, egg->y, egg->z);
if (prev_actor) {
if (prev_actor->get_obj_n() != obj->obj_n
|| !actor_manager->toss_actor_get_location(egg->x, egg->y, egg->z, 3, 2, &actor_loc)
|| !actor_manager->toss_actor_get_location(egg->x, egg->y, egg->z, 2, 3, &actor_loc))
actor_manager->toss_actor_get_location(egg->x, egg->y, egg->z, 4, 4, &actor_loc);
}
uint8 worktype = get_worktype(obj);
actor_manager->create_temp_actor(obj->obj_n, obj->status, actor_loc.x, actor_loc.y, actor_loc.z, alignment, worktype, &new_actor);
/*
// try to group actors of the same type first (FIXME: maybe this should use alignment/quality)
if(prev_actor->get_obj_n() != new_actor->get_obj_n() || !actor_manager->toss_actor(new_actor, 3, 2) || !actor_manager->toss_actor(new_actor, 2, 3))
actor_manager->toss_actor(new_actor, 4, 4);
*/
} else {
/* spawn temp object */
spawned_obj = new Obj();
spawned_obj->obj_n = obj->obj_n;
//spawned_obj->x = egg->x+i; // line objects up in a row
spawned_obj->x = egg->x; // regeants all grow at the same location
spawned_obj->y = egg->y;
spawned_obj->z = egg->z;
spawned_obj->qty = 1; // (it already spawns qty objects with the loop)
spawned_obj->status |= OBJ_STATUS_TEMPORARY | OBJ_STATUS_OK_TO_TAKE;
obj_manager->add_obj(spawned_obj, true); // addOnTop
}
}
}
return true;
}
}
return false;
}
uint8 EggManager::get_worktype(const Obj *embryo) {
if (gametype == NUVIE_GAME_U6
&& (embryo->obj_n == OBJ_U6_WINGED_GARGOYLE || embryo->obj_n == OBJ_U6_GARGOYLE)
&& (Game::get_game()->get_party()->has_obj(OBJ_U6_AMULET_OF_SUBMISSION, 0, false)
|| Game::get_game()->get_party()->contains_actor(164))) { // Beh lem
return WORKTYPE_U6_ANIMAL_WANDER;
}
return embryo->quality;
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,90 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_EGG_MANAGER_H
#define NUVIE_CORE_EGG_MANAGER_H
#include "ultima/shared/std/string.h"
#include "ultima/nuvie/core/obj_manager.h"
namespace Ultima {
namespace Nuvie {
struct Egg {
bool seen_egg;
Obj *obj;
Egg() {
seen_egg = false;
obj = nullptr;
};
};
class Configuration;
class ActorManager;
class Actor;
class Map;
class EggManager {
ActorManager *actor_manager;
ObjManager *obj_manager;
nuvie_game_t gametype; // what game is being played?
Std::list<Egg *> egg_list;
public:
EggManager(nuvie_game_t type);
~EggManager();
void set_actor_manager(ActorManager *am) {
actor_manager = am;
}
void set_obj_manager(ObjManager *om) {
obj_manager = om;
}
void clean(bool keep_obj = true);
void add_egg(Obj *egg);
void remove_egg(Obj *egg, bool keep_egg = true);
void set_egg_visibility(bool show_eggs);
bool spawn_egg(Obj *egg, uint8 hatch_probability);
void spawn_eggs(uint16 x, uint16 y, uint8 z, bool teleport = false);
Std::list<Egg *> *get_egg_list() {
return &egg_list;
};
bool is_spawning_actors() const {
return !not_spawning_actors;
}
void set_spawning_actors(bool spawning) {
not_spawning_actors = !spawning;
}
protected:
uint8 get_worktype(const Obj *embryo);
bool not_spawning_actors;
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,482 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_EVENT_H
#define NUVIE_CORE_EVENT_H
#include "ultima/shared/std/containers.h"
#include "ultima/shared/std/string.h"
#include "ultima/nuvie/misc/call_back.h"
#include "ultima/nuvie/keybinding/keys_enum.h"
#include "ultima/nuvie/core/obj_manager.h"
namespace Ultima {
namespace Nuvie {
class Actor;
class CallbackTarget;
class Configuration;
class Converse;
class Book;
class Game;
class TimeQueue;
class MapWindow;
class MsgScroll;
class GameClock;
class Player;
class ViewManager;
class UseCode;
class GUI;
class GUI_Dialog;
class Magic;
class KeyBinder;
class FpsCounter;
class ScriptThread;
#define NUVIE_INTERVAL 50
#define PUSH_FROM_PLAYER false
#define PUSH_FROM_OBJECT true
#define BUTTON_MASK(MB) (1 << ((int)(MB) - 1))
enum EventMode {
LOOK_MODE = 0,
USE_MODE,
GET_MODE,
MOVE_MODE,
DROP_MODE,
TALK_MODE, /* finding an actor to talk to */
ATTACK_MODE,
PUSH_MODE,
REST_MODE, /* modes before this need targets if using the command bar selected action */
CAST_MODE,
COMBAT_MODE, /* only used to cancel previous actions */
SPELL_MODE, //direct spell casting without spell select etc.
EQUIP_MODE,
WAIT_MODE, /* waiting for something, optionally display prompt when finished */
INPUT_MODE,
MULTIUSE_MODE,
KEYINPUT_MODE,
SCRIPT_MODE
};
extern uint32 nuvieGameCounter;
// type of input that event may collect and send somewhere
#define EVENTINPUT_MAPCOORD 0
#define EVENTINPUT_KEY 1
#define EVENTINPUT_STRING 2
#define EVENTINPUT_OBJECT 3
#define EVENTINPUT_MAPCOORD_DIR 4
#define EVENTINPUT_SPELL_NUM 5
/**
* Joystick actions mapped to dummy unused keycode values
*/
const Common::KeyCode FIRST_JOY = (Common::KeyCode)400;
const Common::KeyCode JOY_UP = FIRST_JOY; // PS d-pad when analog is disabled. left stick when enabled
const Common::KeyCode JOY_DOWN = (Common::KeyCode)(FIRST_JOY + 1);
const Common::KeyCode JOY_LEFT = (Common::KeyCode)(FIRST_JOY + 2);
const Common::KeyCode JOY_RIGHT = (Common::KeyCode)(FIRST_JOY + 3);
const Common::KeyCode JOY_RIGHTUP = (Common::KeyCode)(FIRST_JOY + 4);
const Common::KeyCode JOY_RIGHTDOWN = (Common::KeyCode)(FIRST_JOY + 5);
const Common::KeyCode JOY_LEFTUP = (Common::KeyCode)(FIRST_JOY + 6);
const Common::KeyCode JOY_LEFTDOWN = (Common::KeyCode)(FIRST_JOY + 7);
const Common::KeyCode JOY_UP2 = (Common::KeyCode)(FIRST_JOY + 8); // PS right stick when analog is enabled
const Common::KeyCode JOY_DOWN2 = (Common::KeyCode)(FIRST_JOY + 9);
const Common::KeyCode JOY_LEFT2 = (Common::KeyCode)(FIRST_JOY + 10);
const Common::KeyCode JOY_RIGHT2 = (Common::KeyCode)(FIRST_JOY + 11);
const Common::KeyCode JOY_RIGHTUP2 = (Common::KeyCode)(FIRST_JOY + 12);
const Common::KeyCode JOY_RIGHTDOWN2 = (Common::KeyCode)(FIRST_JOY + 13);
const Common::KeyCode JOY_LEFTUP2 = (Common::KeyCode)(FIRST_JOY + 14);
const Common::KeyCode JOY_LEFTDOWN2 = (Common::KeyCode)(FIRST_JOY + 15);
const Common::KeyCode JOY_UP3 = (Common::KeyCode)(FIRST_JOY + 16);
const Common::KeyCode JOY_DOWN3 = (Common::KeyCode)(FIRST_JOY + 17);
const Common::KeyCode JOY_LEFT3 = (Common::KeyCode)(FIRST_JOY + 18);
const Common::KeyCode JOY_RIGHT3 = (Common::KeyCode)(FIRST_JOY + 19);
const Common::KeyCode JOY_RIGHTUP3 = (Common::KeyCode)(FIRST_JOY + 20);
const Common::KeyCode JOY_RIGHTDOWN3 = (Common::KeyCode)(FIRST_JOY + 21);
const Common::KeyCode JOY_LEFTUP3 = (Common::KeyCode)(FIRST_JOY + 22);
const Common::KeyCode JOY_LEFTDOWN3 = (Common::KeyCode)(FIRST_JOY + 23);
const Common::KeyCode JOY_UP4 = (Common::KeyCode)(FIRST_JOY + 24);
const Common::KeyCode JOY_DOWN4 = (Common::KeyCode)(FIRST_JOY + 25);
const Common::KeyCode JOY_LEFT4 = (Common::KeyCode)(FIRST_JOY + 26);
const Common::KeyCode JOY_RIGHT4 = (Common::KeyCode)(FIRST_JOY + 27);
const Common::KeyCode JOY_RIGHTUP4 = (Common::KeyCode)(FIRST_JOY + 28);
const Common::KeyCode JOY_RIGHTDOWN4 = (Common::KeyCode)(FIRST_JOY + 29);
const Common::KeyCode JOY_LEFTUP4 = (Common::KeyCode)(FIRST_JOY + 30);
const Common::KeyCode JOY_LEFTDOWN4 = (Common::KeyCode)(FIRST_JOY + 31);
const Common::KeyCode JOY_HAT_UP = (Common::KeyCode)(FIRST_JOY + 32); // PS d-pad when analog is enabled
const Common::KeyCode JOY_HAT_DOWN = (Common::KeyCode)(FIRST_JOY + 33);
const Common::KeyCode JOY_HAT_LEFT = (Common::KeyCode)(FIRST_JOY + 34);
const Common::KeyCode JOY_HAT_RIGHT = (Common::KeyCode)(FIRST_JOY + 35);
const Common::KeyCode JOY_HAT_RIGHTUP = (Common::KeyCode)(FIRST_JOY + 36);
const Common::KeyCode JOY_HAT_RIGHTDOWN = (Common::KeyCode)(FIRST_JOY + 37);
const Common::KeyCode JOY_HAT_LEFTUP = (Common::KeyCode)(FIRST_JOY + 38);
const Common::KeyCode JOY_HAT_LEFTDOWN = (Common::KeyCode)(FIRST_JOY + 39);
const Common::KeyCode JOY0 = (Common::KeyCode)(FIRST_JOY + 40); // PS triangle
const Common::KeyCode JOY1 = (Common::KeyCode)(FIRST_JOY + 41); // PS circle
const Common::KeyCode JOY2 = (Common::KeyCode)(FIRST_JOY + 42); // PS x
const Common::KeyCode JOY3 = (Common::KeyCode)(FIRST_JOY + 43); // PS square
const Common::KeyCode JOY4 = (Common::KeyCode)(FIRST_JOY + 44); // PS L2
const Common::KeyCode JOY5 = (Common::KeyCode)(FIRST_JOY + 45); // PS R2
const Common::KeyCode JOY6 = (Common::KeyCode)(FIRST_JOY + 46); // PS L1
const Common::KeyCode JOY7 = (Common::KeyCode)(FIRST_JOY + 47); // PS R1
const Common::KeyCode JOY8 = (Common::KeyCode)(FIRST_JOY + 48); // PS select
const Common::KeyCode JOY9 = (Common::KeyCode)(FIRST_JOY + 49); // PS start
const Common::KeyCode JOY10 = (Common::KeyCode)(FIRST_JOY + 50); // PS L3 (analog must be enabled)
const Common::KeyCode JOY11 = (Common::KeyCode)(FIRST_JOY + 51); // PS R3 (analog must be enabled)
const Common::KeyCode JOY12 = (Common::KeyCode)(FIRST_JOY + 52);
const Common::KeyCode JOY13 = (Common::KeyCode)(FIRST_JOY + 53);
const Common::KeyCode JOY14 = (Common::KeyCode)(FIRST_JOY + 54);
const Common::KeyCode JOY15 = (Common::KeyCode)(FIRST_JOY + 55);
const Common::KeyCode JOY16 = (Common::KeyCode)(FIRST_JOY + 56);
const Common::KeyCode JOY17 = (Common::KeyCode)(FIRST_JOY + 57);
const Common::KeyCode JOY18 = (Common::KeyCode)(FIRST_JOY + 58);
const Common::KeyCode JOY19 = (Common::KeyCode)(FIRST_JOY + 59);
struct EventInput_s {
uint8 type; // 0=loc,1=key,2=str,3=obj,4=actor
// union
// {
Common::KeyCode key; // last key entered, if capturing input
ActionKeyType action_key_type; // last ActionKeyType entered if capturing input
MapCoord *loc; // target location, or direction if relative ???
Std::string *str; // ???
// };
void set_loc(const MapCoord &c);
EventInput_s() : loc(0), str(0), obj(0), actor(0), get_direction(false), get_text(false),
target_init(0), select_from_inventory(false), select_range(0), key(Common::KEYCODE_INVALID),
action_key_type(ActionKeyType::CANCEL_ACTION_KEY), spell_num(0), type(0) {
}
~EventInput_s();
Obj *obj; // top object at loc (or object from inventory)
Actor *actor; // actor at loc
bool get_direction; // if true, entering directions selects a target
bool get_text; // if true, the MsgScroll is polled for text input
MapCoord *target_init; // where MapWindow cursor is centered when targeting
bool select_from_inventory; // if true, objects from inventory will be selected (and not from the map)
uint8 select_range; // limits movement of MapWindow cursor from center
sint16 spell_num;
};
typedef struct EventInput_s EventInput;
class Events : public CallBack {
friend class Magic; // FIXME
private:
const Configuration *config;
GUI *gui;
Game *game;
ObjManager *obj_manager;
MapWindow *map_window;
MsgScroll *scroll;
GameClock *clock;
Player *player;
Converse *converse;
ViewManager *view_manager;
UseCode *usecode;
Magic *magic;
KeyBinder *keybinder;
GUI_Dialog *gamemenu_dialog;
GUI_Dialog *assetviewer_dialog;
Common::Event event;
EventMode mode, last_mode;
EventInput input; // collected/received input (of any type)
// Std::vector<EventMode> mode_stack; // current mode is at the end of the list
int ts; //timestamp for TimeLeft() method.
int altCodeVal;
uint16 active_alt_code; // alt-code that needs more input
uint8 alt_code_input_num; // alt-code can get multiple inputs
TimeQueue *time_queue, *game_time_queue;
Obj *drop_obj;
uint16 drop_qty;
sint32 drop_x, drop_y; // only to allow pre-targeting from MapWindow, feel free to ignore this
uint8 rest_time; // How many hours?
uint8 rest_guard; // Who will guard?
Obj *push_obj;
Actor *push_actor;
bool drop_from_key;
bool showingDialog;
bool showingQuitDialog;
bool ignore_timeleft; // do not wait for NUVIE_INTERVAL
bool move_in_inventory;
bool in_control_cheat;
bool looking_at_spellbook;
bool direction_selects_target;
bool _keymapperStateBeforeKEYINPUT;
uint32 fps_timestamp;
uint16 fps_counter;
FpsCounter *fps_counter_widget;
ScriptThread *scriptThread;
Common::Point _mousePos;
uint8 _buttonsDown;
static Events *g_events;
protected:
inline uint32 TimeLeft();
uint16 callback(uint16 msg, CallBack *caller, void *data = nullptr) override;
bool handleSDL_KEYDOWN(const Common::Event *event);
const char *print_mode(EventMode mode);
void try_next_attack();
public:
enum MouseButton {
BUTTON_NONE = 0,
BUTTON_LEFT = 1,
BUTTON_RIGHT = 2,
BUTTON_MIDDLE = 3,
MOUSE_LAST
};
Events(const Configuration *cfg);
~Events() override;
void clear();
bool init(ObjManager *om, MapWindow *mw, MsgScroll *ms, Player *p, Magic *mg,
GameClock *gc, ViewManager *vm, UseCode *uc, GUI *g, KeyBinder *kb);
GUI_Dialog *get_gamemenu_dialog() {
return gamemenu_dialog;
}
TimeQueue *get_time_queue() {
return time_queue;
}
TimeQueue *get_game_time_queue() {
return game_time_queue;
}
EventMode get_mode() const {
return mode;
}
EventMode get_last_mode() const {
return last_mode;
}
void set_mode(EventMode new_mode);
bool is_direction_selecting_targets() const {
return direction_selects_target;
}
void set_direction_selects_target(bool val) {
direction_selects_target = val;
}
/**
* Return the mouse position
*/
Common::Point getMousePos() const {
return _mousePos;
}
/**
* Returns the mouse buttons states
*/
byte getButtonState() const {
return _buttonsDown;
}
bool using_pickpocket_cheat;
bool cursor_mode;
void update_timers();
bool update();
static MouseButton whichButton(Common::EventType type);
bool pollEvent(Common::Event &event);
bool handleEvent(const Common::Event *event);
void request_input(CallBack *caller, void *user_data = nullptr);
void target_spell();
void close_spellbook();
// Prompt for input.
// obsolete:
// void useselect_mode(Obj *src, const char *prompt = nullptr); // deprecated
// void freeselect_mode(Obj *src, const char *prompt = nullptr); // deprecated
void get_scroll_input(const char *allowed = nullptr, bool can_escape = true, bool using_target_cursor = false, bool set_numbers_only_to_true = true);
void get_inventory_obj(Actor *actor, bool getting_target = true);
void get_spell_num(Actor *caster, Obj *spell_container);
// void get_amount();
void get_direction(const char *prompt);
void get_direction(const MapCoord &from, const char *prompt);
void get_target(const char *prompt);
void get_target(const MapCoord &init, const char *prompt);
// void get_obj_from_inventory(Actor *actor, const char *prompt);
void display_portrait(Actor *actor, const char *name = nullptr);
// Start a new action, setting a new mode and prompting for input.
bool newAction(EventMode new_mode);
// void doAction(sint16 rel_x = 0, sint16 rel_y = 0);
// void doAction(Obj *obj);
void doAction();
void cancelAction();
void endAction(bool prompt = false);
// Send input back to Events, performing an action for the current mode.
bool select_obj(Obj *obj, Actor *actor = nullptr);
bool select_view_obj(Obj *obj, Actor *actor);
bool select_actor(Actor *actor);
bool select_direction(sint16 rel_x, sint16 rel_y);
bool select_target(uint16 x, uint16 y, uint8 z = 0);
bool select_party_member(uint8 num);
bool select_spell_num(sint16 spell_num);
// bool select_obj(Obj *obj = nullptr, Actor *actor = nullptr);
// bool select_obj(sint16 rel_x, sint16 rel_y);
// There is no "select_text", as Events polls MsgScroll for new input.
// Similarly, a "select_key" is unnecessary. The following method
// starts sending all keyboard input to 'caller'. (with the CB_DATA_READY message)
void key_redirect(CallBack *caller, void *user_data);
void cancel_key_redirect();
/* These will be replaced in the future with an InputAction class. */
bool move(sint16 rel_x, sint16 rel_y);
bool use_start();
bool use(sint16 rel_x, sint16 rel_y);
bool use(const MapCoord &coord);
bool use(Obj *obj);
bool use(Actor *actor, uint16 x, uint16 y);
bool get_start();
bool get(const MapCoord &coord);
bool get(sint16 rel_x, sint16 rel_y);
bool perform_get(Obj *obj, Obj *container_obj = nullptr, Actor *actor = nullptr);
bool look_start();
bool lookAtCursor(bool delayed = false, uint16 x = 0, uint16 y = 0, uint8 z = 0, Obj *obj = nullptr, Actor *actor = nullptr);
bool look(Obj *obj);
bool look(Actor *actor);
bool search(Obj *obj);
bool talk_start();
bool talk_cursor();
bool talk(Actor *actor);
bool talk(Obj *obj);
bool perform_talk(Actor *actor);
bool attack();
bool push_start();
bool pushFrom(Obj *obj);
bool pushFrom(sint16 rel_x, sint16 rel_y);
bool pushFrom(const MapCoord &target);
bool pushTo(Obj *obj, Actor *actor);
bool pushTo(sint16 rel_x, sint16 rel_y, bool push_from = PUSH_FROM_PLAYER);
void solo_mode(uint32 actor_num);
bool party_mode();
bool toggle_combat();
bool ready(Obj *obj, Actor *actor = nullptr);
bool unready(Obj *obj);
bool drop_start();
bool drop_select(Obj *obj, uint16 qty = 0);
bool drop_count(uint16 qty);
bool perform_drop();
void set_drop_from_key(bool closing_gumps) {
drop_from_key = closing_gumps;
}
bool drop(Obj *obj, uint16 qty, uint16 x, uint16 y);
bool drop(uint16 x, uint16 y) {
return (drop(drop_obj, drop_qty, x, y));
}
void set_drop_target(uint16 x, uint16 y) {
drop_x = sint32(x);
drop_y = sint32(y);
}
bool can_move_obj_between_actors(Obj *obj, Actor *src_actor, Actor *target_actor, bool display_name = false);
void display_not_aboard_vehicle(bool show_prompt = true);
void display_move_text(Actor *target_actor, Obj *obj);
bool can_get_to_actor(const Actor *actor, uint16 x, uint16 y);
bool using_control_cheat() const {
return in_control_cheat;
}
void set_control_cheat(bool control_cheat) {
in_control_cheat = control_cheat;
}
bool is_looking_at_spellbook() const {
return looking_at_spellbook;
}
void set_looking_at_spellbook(bool looking) {
looking_at_spellbook = looking;
}
bool rest();
bool rest_input(uint16 input);
void cast_spell_directly(uint8 spell_num);
bool can_target_icon(); // Target the actor or container tile in inventory and party view
// these are both for mouse-using convenience
void walk_to_mouse_cursor(uint32 mx, uint32 my);
void multiuse(uint16 wx, uint16 wy);
void alt_code(int c);
void alt_code_input(const char *in);
void clear_alt_code() { altCodeVal = 0; }
void toggleAltCodeMode(bool enable);
void appendAltCode(int code);
bool alt_code_teleport(const char *location_string);
void alt_code_infostring();
void alt_code_teleport_menu(uint32 selection);
bool alt_code_teleport_to_person(uint32 npc);
void wait();
void set_ignore_timeleft(bool newsetting) {
ignore_timeleft = newsetting;
}
EventInput *get_input() {
return &input;
}
// These cursor methods are use to make sure Events knows where the cursor is
// when objects are selected with ENTER. (since MapWindow and InventoryView
// may each independently show/hide their own cursors)
void moveCursorToMapWindow(bool ToggleCursor = false);
void moveCursorToInventory();
void toggleFpsDisplay();
void close_gumps();
bool do_not_show_target_cursor;
bool dont_show_target_cursor() const;
bool input_really_needs_directon() const;
void quitDialog();
void gameMenuDialog();
void assetViewer();
bool actor_exists(const Actor *a) const;
/* FIXME: Some of the above (action) functions can be removed from public, so
that we don't need to check for WAIT mode in all of them. */
/**
* Gets a reference to the events manager
*/
static Events *get() { return g_events; }
};
extern bool shouldQuit();
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,692 @@
/* 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/conf/configuration.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/gui/gui.h"
#include "ultima/nuvie/gui/widgets/console.h"
#include "ultima/nuvie/screen/dither.h"
#include "ultima/nuvie/sound/sound_manager.h"
#include "ultima/nuvie/actors/actor.h"
#include "ultima/nuvie/script/script.h"
#include "ultima/nuvie/screen/screen.h"
#include "ultima/nuvie/screen/game_palette.h"
#include "ultima/nuvie/core/game_clock.h"
#include "ultima/nuvie/core/egg_manager.h"
#include "ultima/nuvie/core/obj_manager.h"
#include "ultima/nuvie/actors/actor_manager.h"
#include "ultima/nuvie/core/player.h"
#include "ultima/nuvie/core/party.h"
#include "ultima/nuvie/core/converse.h"
#include "ultima/nuvie/gui/widgets/converse_gump.h"
#include "ultima/nuvie/gui/widgets/converse_gump_wou.h"
#include "ultima/nuvie/fonts/font_manager.h"
#include "ultima/nuvie/views/view_manager.h"
#include "ultima/nuvie/core/effect_manager.h"
#include "ultima/nuvie/core/magic.h"
#include "ultima/nuvie/gui/widgets/msg_scroll.h"
#include "ultima/nuvie/gui/widgets/msg_scroll_new_ui.h"
#include "ultima/nuvie/core/map.h"
#include "ultima/nuvie/gui/widgets/map_window.h"
#include "ultima/nuvie/core/events.h"
#include "ultima/nuvie/portraits/portrait.h"
#include "ultima/nuvie/gui/widgets/background.h"
#include "ultima/nuvie/gui/widgets/command_bar.h"
#include "ultima/nuvie/gui/widgets/command_bar_new_ui.h"
#include "ultima/nuvie/views/party_view.h"
#include "ultima/nuvie/views/actor_view.h"
#include "ultima/nuvie/usecode/usecode.h"
#include "ultima/nuvie/usecode/u6_usecode.h"
#include "ultima/nuvie/core/cursor.h"
#include "ultima/nuvie/core/weather.h"
#include "ultima/nuvie/core/book.h"
#include "ultima/nuvie/keybinding/keys.h"
#include "ultima/nuvie/files/utils.h"
#include "ultima/nuvie/core/game.h"
#include "ultima/nuvie/nuvie.h"
#include "common/system.h"
namespace Ultima {
namespace Nuvie {
Game *Game::game = nullptr;
Game::Game(Configuration *cfg, Events *evt, Screen *scr, GUI *g, nuvie_game_t type, SoundManager *sm)
: config(cfg), event(evt), gui(g), screen(scr), game_type(type),
sound_manager(sm), script(nullptr), background(nullptr),
cursor(nullptr), dither(nullptr), tile_manager(nullptr),
obj_manager(nullptr), palette(nullptr), font_manager(nullptr),
scroll(nullptr), game_map(nullptr), map_window(nullptr),
actor_manager(nullptr), player(nullptr), converse(nullptr),
conv_gump(nullptr), command_bar(nullptr), new_command_bar(nullptr),
_clock(nullptr), party(nullptr), portrait(nullptr),
view_manager(nullptr), egg_manager(nullptr), usecode(nullptr),
effect_manager(nullptr), weather(nullptr), magic(nullptr),
book(nullptr), keybinder(nullptr), _playing(true),
converse_gump_type(CONVERSE_GUMP_DEFAULT),
pause_flags(PAUSE_UNPAUSED), pause_user_count(0),
ignore_event_delay(0), unlimited_casting(false),
god_mode_enabled(false), armageddon(false), ethereal(false),
free_balloon_movement(false), converse_gump_width(0),
min_converse_gump_width(0), force_solid_converse_bg(false) {
game = this;
config->value("config/cheats/enabled", cheats_enabled, false);
config->value("config/cheats/enable_hackmove", is_using_hackmove, false);
config->value("config/input/enabled_dragging", dragging_enabled, true);
config->value("config/general/use_text_gumps", using_text_gumps, false);
config->value(config_get_game_key(config) + "/roof_mode", roof_mode, false);
config->value("config/input/doubleclick_opens_containers", open_containers, false);
int value;
uint16 screen_width = gui->get_width();
uint16 screen_height = gui->get_height();
init_game_style();
if (is_orig_style()) {
game_width = 320;
game_height = 200;
} else {
config->value("config/video/game_width", value, 320);
game_width = (value < screen_width) ? value : screen_width;
config->value("config/video/game_height", value, 200);
game_height = (value < screen_height) ? value : screen_height;
if (game_width < 320)
game_width = 320;
if (game_height < 200)
game_height = 200;
if (is_original_plus_full_map() && screen_height <= 200) // not tall enough to show extra map space
game_style = NUVIE_STYLE_ORIG_PLUS_CUTOFF_MAP;
}
string game_position;
config->value("config/video/game_position", game_position, "center");
if (game_position == "upper_left")
game_x_offset = game_y_offset = 0;
else { // center
game_x_offset = (screen_width - game_width) / 2;
game_y_offset = (screen_height - game_height) / 2;
}
effect_manager = new EffectManager;
init_cursor();
keybinder = new KeyBinder(config);
}
Game::~Game() {
// note: don't delete objects that are added to the GUI object via
// AddWidget()!
if (dither) delete dither;
if (tile_manager) delete tile_manager;
if (obj_manager) delete obj_manager;
if (palette) delete palette;
if (font_manager) delete font_manager;
//delete scroll;
if (game_map) delete game_map;
if (actor_manager) delete actor_manager;
//delete map_window;
// If conversation active, must be deleted before player as it resets
// player flags.
if (converse) delete converse;
if (player) delete player;
//delete background;
if (_clock) delete _clock;
if (party) delete party;
if (portrait) delete portrait;
if (view_manager) delete view_manager;
if (sound_manager) delete sound_manager;
if (gui) delete gui;
if (usecode) delete usecode;
if (effect_manager) delete effect_manager;
if (cursor) delete cursor;
if (egg_manager) delete egg_manager;
if (weather) delete weather;
if (magic) delete magic;
if (book) delete book;
if (keybinder) delete keybinder;
}
bool Game::shouldQuit() const {
return !_playing || g_engine->shouldQuit();
}
bool Game::loadGame(Script *s) {
dither = new Dither(config);
script = s;
//sound_manager->LoadSongs(nullptr);
//sound_manager->LoadObjectSamples(nullptr);
palette = new GamePalette(screen, config);
_clock = new GameClock(game_type);
background = new Background(config);
background->init();
background->Hide();
if (is_original_plus_full_map() == false) // need to render before map window
gui->AddWidget(background);
font_manager = new FontManager(config);
font_manager->init(game_type);
if (!is_new_style()) {
scroll = new MsgScroll(config, font_manager->get_font(0));
} else {
scroll = new MsgScrollNewUI(config, screen);
}
game_map = new Map(config);
egg_manager = new EggManager(game_type);
tile_manager = new TileManager(config);
if (tile_manager->loadTiles() == false)
return false;
ConsoleAddInfo("Loading ObjManager()");
obj_manager = new ObjManager(config, tile_manager, egg_manager);
if (game_type == NUVIE_GAME_U6) {
book = new Book(config);
if (book->init() == false)
return false;
config->value(config_get_game_key(config) + "/free_balloon_movement", free_balloon_movement, false);
}
// Correct usecode class for each game
switch (game_type) {
case NUVIE_GAME_U6 :
usecode = (UseCode *) new U6UseCode(this, config);
break;
case NUVIE_GAME_MD :
usecode = (UseCode *) new UseCode(this, config);
break;
case NUVIE_GAME_SE :
usecode = (UseCode *) new UseCode(this, config);
break;
}
obj_manager->set_usecode(usecode);
//obj_manager->loadObjs();
ConsoleAddInfo("Loading map data.");
game_map->loadMap(tile_manager, obj_manager);
egg_manager->set_obj_manager(obj_manager);
ConsoleAddInfo("Loading actor data.");
actor_manager = new ActorManager(config, game_map, tile_manager, obj_manager, _clock);
game_map->set_actor_manager(actor_manager);
egg_manager->set_actor_manager(actor_manager);
map_window = new MapWindow(config, game_map);
map_window->init(tile_manager, obj_manager, actor_manager);
map_window->Hide();
gui->AddWidget(map_window);
if (is_original_plus_full_map()) // need to render after map window
gui->AddWidget(background);
weather = new Weather(config, _clock, game_type);
// if(!is_new_style()) // Everyone always uses original style command bar now.
{
command_bar = new CommandBar(this);
bool using_new_command_bar;
config->value("config/input/new_command_bar", using_new_command_bar, false);
if (using_new_command_bar) {
init_new_command_bar();
}
}
// else
// command_bar = new CommandBarNewUI(this);
command_bar->Hide();
gui->AddWidget(command_bar);
player = new Player(config);
party = new Party(config);
player->init(obj_manager, actor_manager, map_window, _clock, party);
party->init(this, actor_manager);
portrait = newPortrait(game_type, config);
if (portrait->init() == false)
return false;
view_manager = new ViewManager(config);
view_manager->init(gui, font_manager->get_font(0), party, player, tile_manager, obj_manager, portrait);
scroll->Hide();
gui->AddWidget(scroll);
//map_window->set_windowSize(11,11);
init_converse_gump_settings();
init_converse();
usecode->init(obj_manager, game_map, player, scroll);
if (game_type == NUVIE_GAME_U6) {
magic = new Magic();
}
event->init(obj_manager, map_window, scroll, player, magic, _clock, view_manager, usecode, gui, keybinder);
if (game_type == NUVIE_GAME_U6) {
magic->init(event);
}
if (g_engine->journeyOnwards() == false) {
return false;
}
ConsoleAddInfo("Polishing Anhk");
//ConsolePause();
ConsoleHide();
// if(!is_new_style())
{
if (is_orig_style())
command_bar->Show();
else {
bool show;
Std::string show_cb;
config->value(config_get_game_key(config) + "/show_orig_style_cb", show_cb, "default");
if (show_cb == "default") {
if (is_new_style())
show = false;
else
show = true;
} else if (show_cb == "no")
show = false;
else
show = true;
if (show)
command_bar->Show();
}
}
if (!is_new_style() || screen->get_width() != get_game_width() || screen->get_height() != get_game_height()) {
background->Show();
}
map_window->Show();
scroll->Show();
view_manager->update();
if (cursor)
cursor->show();
return true;
}
void Game::init_converse_gump_settings() {
if (is_new_style())
converse_gump_type = CONVERSE_GUMP_DEFAULT;
else {
converse_gump_type = get_converse_gump_type_from_config(config);
}
Std::string width_str;
int gump_w = get_game_width();
if (game_type == NUVIE_GAME_MD)
min_converse_gump_width = 298;
else if (game_type == NUVIE_GAME_SE)
min_converse_gump_width = 301;
else // U6
min_converse_gump_width = 286;
config->value(config_get_game_key(config) + "/converse_width", width_str, "default");
if (!game->is_orig_style()) {
if (width_str == "default") {
int map_width = get_game_width();
if (is_original_plus())
map_width += - background->get_border_width() - 1;
if (map_width > min_converse_gump_width * 1.5) // big enough that we probably don't want to take up the whole screen
gump_w = min_converse_gump_width;
else if (game->is_original_plus() && map_width >= min_converse_gump_width) // big enough to draw without going over the UI
gump_w = map_width;
} else {
config->value(config_get_game_key(config) + "/converse_width", gump_w, gump_w);
if (gump_w < min_converse_gump_width)
gump_w = min_converse_gump_width;
else if (gump_w > get_game_width())
gump_w = get_game_width();
}
}
converse_gump_width = (uint16)gump_w;
if ((is_original_plus_cutoff_map() && get_game_width() - background->get_border_width() < min_converse_gump_width)
|| game->is_orig_style())
force_solid_converse_bg = true;
else
force_solid_converse_bg = false;
}
void Game::init_converse() {
converse = new Converse();
if (using_new_converse_gump()) {
conv_gump = new ConverseGump(config, font_manager->get_font(0), screen);
conv_gump->Hide();
gui->AddWidget(conv_gump);
converse->init(config, game_type, conv_gump, actor_manager, _clock, player, view_manager, obj_manager);
} else if (game_type == NUVIE_GAME_U6 && converse_gump_type == CONVERSE_GUMP_DEFAULT) {
converse->init(config, game_type, scroll, actor_manager, _clock, player, view_manager, obj_manager);
} else {
ConverseGumpWOU *gump = new ConverseGumpWOU(config, font_manager->get_font(0), screen);
gump->Hide();
gui->AddWidget(gump);
converse->init(config, game_type, gump, actor_manager, _clock, player, view_manager, obj_manager);
}
}
void Game::set_converse_gump_type(ConverseGumpType new_type) {
if (converse)
delete converse;
converse_gump_type = new_type;
init_converse();
}
bool Game::using_new_converse_gump() {
return (is_new_style() || converse_gump_type == CONVERSE_GUMP_U7_STYLE);
}
void Game::delete_new_command_bar() {
if (new_command_bar == nullptr)
return;
new_command_bar->Delete();
new_command_bar = nullptr;
}
void Game::init_new_command_bar() {
if (new_command_bar != nullptr)
return;
new_command_bar = new CommandBarNewUI(this);
new_command_bar->Hide();
gui->AddWidget(new_command_bar);
}
void Game::init_cursor() {
if (!cursor)
cursor = new Cursor();
if (cursor->init(config, screen, game_type))
g_system->showMouse(false); // won't need the system default
else {
delete cursor;
cursor = nullptr; // no game cursor
}
}
void Game::init_game_style() {
string game_style_str;
config->value("config/video/game_style", game_style_str, "original");
if (game_style_str == "new")
game_style = NUVIE_STYLE_NEW;
else if (game_style_str == "original+")
game_style = NUVIE_STYLE_ORIG_PLUS_CUTOFF_MAP;
else if (game_style_str == "original+_full_map")
game_style = NUVIE_STYLE_ORIG_PLUS_FULL_MAP;
else
game_style = NUVIE_STYLE_ORIG;
}
bool Game::doubleclick_opens_containers() {
if (open_containers || is_new_style())
return true;
else
return false;
}
bool Game::using_hackmove() {
if (cheats_enabled)
return is_using_hackmove;
else
return false;
}
void Game::set_hackmove(bool hackmove) {
is_using_hackmove = hackmove;
map_window->set_interface();
}
bool Game::set_mouse_pointer(uint8 ptr_num) {
return (cursor && cursor->set_pointer(ptr_num));
}
//FIXME pausing inside a script function causes problems with yield/resume logic.
void Game::set_pause_flags(GamePauseState state) {
pause_flags = state; // set
}
void Game::unpause_all() {
// DEBUG(0, LEVEL_DEBUGGING,"Unpause ALL!\n");
unpause_user();
unpause_anims();
unpause_world();
}
void Game::unpause_user() {
if (pause_user_count > 0)
pause_user_count--;
if (pause_user_count == 0) {
set_pause_flags((GamePauseState)(pause_flags & ~PAUSE_USER));
//if(event->get_mode() == WAIT_MODE)
// event->endAction(); // change to MOVE_MODE, hide cursors
if (gui->get_block_input())
gui->unblock();
}
// DEBUG(0, LEVEL_DEBUGGING, "unpause user count=%d!\n", pause_user_count);
}
void Game::unpause_anims() {
set_pause_flags((GamePauseState)(pause_flags & ~PAUSE_ANIMS));
}
void Game::unpause_world() {
set_pause_flags((GamePauseState)(pause_flags & ~PAUSE_WORLD));
if (actor_manager->get_update() == false) // ActorMgr is not running
game->get_actor_manager()->set_update(true); // resume
//if(clock->get_active() == false) // start time
// clock->set_active(true);
}
void Game::pause_all() {
pause_user();
pause_anims();
pause_world();
}
void Game::pause_user() {
set_pause_flags((GamePauseState)(pause_flags | PAUSE_USER));
if (!gui->get_block_input() && pause_user_count == 0)
gui->block();
pause_user_count++;
// DEBUG(0, LEVEL_DEBUGGING, "Pause user count=%d!\n", pause_user_count);
}
void Game::pause_anims() {
set_pause_flags((GamePauseState)(pause_flags | PAUSE_ANIMS));
}
void Game::pause_world() {
set_pause_flags((GamePauseState)(pause_flags | PAUSE_WORLD));
if (actor_manager->get_update() == true) // ActorMgr is running
game->get_actor_manager()->set_update(false); // pause
//if(clock->get_active() == true) // stop time
// clock->set_active(false);
}
void Game::dont_wait_for_interval() {
if (ignore_event_delay < 255)
++ignore_event_delay;
event->set_ignore_timeleft(true);
}
void Game::wait_for_interval() {
if (ignore_event_delay > 0)
--ignore_event_delay;
if (ignore_event_delay == 0)
event->set_ignore_timeleft(false);
}
void Game::time_changed() {
if (!is_new_style()) {
if (game->is_orig_style()) // others constantly update
get_command_bar()->update(); // date & wind
get_view_manager()->get_party_view()->update(); // sky
}
get_map_window()->updateAmbience();
}
// FIXME: should this be in ViewManager?
void Game::stats_changed() {
if (!is_new_style()) {
get_view_manager()->get_actor_view()->update();
get_view_manager()->get_party_view()->update();
}
}
void Game::play() {
pause_flags = PAUSE_UNPAUSED;
//view_manager->set_inventory_mode(1); //FIX
screen->update();
//map_window->drawMap();
map_window->updateBlacking();
while (!shouldQuit()) {
if (cursor) cursor->clear(); // restore cursor area before GUI events
event->update();
if (_clock->get_timer(GAMECLOCK_TIMER_U6_TIME_STOP) == 0) {
palette->rotatePalette();
tile_manager->update();
actor_manager->twitchActors();
}
actor_manager->moveActors(); // update/move actors for this turn
map_window->update();
//map_window->drawMap();
converse->continue_script();
//scroll->updateScroll();
effect_manager->update_effects();
gui->Display();
if (cursor) cursor->display();
screen->performUpdate();
sound_manager->update();
event->wait();
}
return;
}
void Game::update_until_converse_finished() {
while (converse->running()) {
update_once(true, true);
update_once_display();
}
}
void Game::update_once(bool process_gui_input) {
update_once(process_gui_input, false);
}
void Game::update_once(bool process_gui_input, bool run_converse) {
if (cursor) cursor->clear(); // restore cursor area before GUI events
event->update_timers();
Common::Event evt;
while (Events::get()->pollEvent(evt)) {
if (process_gui_input)
gui->HandleEvent(&evt);
}
if (_clock->get_timer(GAMECLOCK_TIMER_U6_TIME_STOP) == 0) {
palette->rotatePalette();
tile_manager->update();
actor_manager->twitchActors();
}
map_window->update();
if (run_converse) {
converse->continue_script();
}
effect_manager->update_effects();
}
void Game::update_once_display() {
gui->Display();
if (cursor) cursor->display();
screen->performUpdate();
sound_manager->update();
event->wait();
}
/* return the fullpath to the datafile. First look for it in the savegame directory.
* Then in the app data directory.
*/
Common::Path Game::get_data_file_path(const Common::Path &datafile) {
Common::Path path("data");
path.joinInPlace(datafile);
if (!file_exists(path)) {
path = gui->get_data_dir().joinInPlace(datafile);
}
return path;
}
uint getRandom(uint maxVal) {
return g_engine->getRandomNumber(maxVal);
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,429 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_GAME_H
#define NUVIE_CORE_GAME_H
#include "ultima/shared/std/containers.h"
#include "ultima/shared/std/string.h"
#include "ultima/nuvie/core/nuvie_defs.h"
namespace Ultima {
namespace Nuvie {
class Configuration;
class Script;
class Screen;
class Background;
class GamePalette;
class FontManager;
class Dither;
class TileManager;
class ObjManager;
class ActorManager;
class Magic;
class Map;
class MapWindow;
class MsgScroll;
class Player;
class Party;
class Converse;
class ConverseGump;
class Cursor;
class GameClock;
class ViewManager;
class Portrait;
class UseCode;
class Events;
class GUI;
class EffectManager;
class SoundManager;
class EggManager;
class CommandBar;
class Weather;
class Book;
class KeyBinder;
typedef enum {
PAUSE_UNPAUSED = 0x00,
PAUSE_USER = 0x01, /* Don't allow user-input */
PAUSE_ANIMS = 0x02, /* TileManager & Palette */
PAUSE_WORLD = 0x04, /* game time doesn't pass, freeze actors */
PAUSE_ALL = 0xFF
} GamePauseState;
class Game {
private:
nuvie_game_t game_type;
uint8 game_style; //new, original, orig_plus_cutoff_map, or orig_plus_full_map
static Game *game;
Configuration *config;
Script *script;
Screen *screen;
Background *background;
GamePalette *palette;
Dither *dither;
FontManager *font_manager;
TileManager *tile_manager;
ObjManager *obj_manager;
ActorManager *actor_manager;
Magic *magic;
Map *game_map;
MapWindow *map_window;
MsgScroll *scroll;
Player *player;
Party *party;
Converse *converse;
ConverseGump *conv_gump;
CommandBar *command_bar;
CommandBar *new_command_bar;
ViewManager *view_manager;
EffectManager *effect_manager;
SoundManager *sound_manager;
EggManager *egg_manager;
GameClock *_clock;
Portrait *portrait;
UseCode *usecode;
Weather *weather;
Cursor *cursor;
Events *event;
GUI *gui;
Book *book;
KeyBinder *keybinder;
GamePauseState pause_flags;
uint16 game_width;
uint16 game_height;
uint16 game_x_offset;
uint16 game_y_offset;
uint16 pause_user_count;
uint16 converse_gump_width;
uint16 min_converse_gump_width;
uint8 ignore_event_delay; // (stack) if non-zero, Events will not periodically wait for NUVIE_INTERVAL
bool is_using_hackmove;
bool dragging_enabled;
bool cheats_enabled;
bool unlimited_casting;
bool god_mode_enabled;
bool armageddon;
bool ethereal;
bool using_text_gumps;
bool open_containers; //doubleclick
ConverseGumpType converse_gump_type;
bool roof_mode;
bool free_balloon_movement;
bool force_solid_converse_bg;
bool _playing;
public:
Game(Configuration *cfg, Events *evt, Screen *scr, GUI *g, nuvie_game_t type, SoundManager *sm);
~Game();
bool loadGame(Script *s);
void init_cursor();
void init_game_style();
void play();
void update_once(bool process_gui_input);
void update_once_display();
void update_until_converse_finished();
bool isLoaded() const {
return script != nullptr;
}
GamePauseState get_pause_flags() const {
return pause_flags;
}
void set_pause_flags(GamePauseState state);
void unpause_all();
void unpause_user();
void unpause_anims();
void unpause_world();
void pause_all();
void pause_user();
void pause_anims();
void pause_world();
bool paused() const {
return pause_flags;
}
bool all_paused() const {
return (pause_flags & PAUSE_ALL);
}
bool user_paused() const {
return (pause_flags & PAUSE_USER);
}
bool anims_paused() const {
return (pause_flags & PAUSE_ANIMS);
}
bool world_paused() const{
return (pause_flags & PAUSE_WORLD);
}
void quit() {
_playing = false;
}
bool shouldQuit() const;
bool set_mouse_pointer(uint8 ptr_num);
void dont_wait_for_interval();
void wait_for_interval();
void time_changed();
void stats_changed();
void init_new_command_bar();
void delete_new_command_bar();
nuvie_game_t get_game_type() const {
return game_type;
}
uint8 get_game_style() const {
return game_style;
}
bool is_original_plus() const {
return (game_style == NUVIE_STYLE_ORIG_PLUS_CUTOFF_MAP || game_style == NUVIE_STYLE_ORIG_PLUS_FULL_MAP);
}
bool is_original_plus_cutoff_map() const {
return (game_style == NUVIE_STYLE_ORIG_PLUS_CUTOFF_MAP);
}
bool is_original_plus_full_map() const {
return (game_style == NUVIE_STYLE_ORIG_PLUS_FULL_MAP);
}
bool is_new_style() const {
return (game_style == NUVIE_STYLE_NEW);
}
bool is_orig_style() const {
return (game_style == NUVIE_STYLE_ORIG);
}
bool doubleclick_opens_containers();
void set_doubleclick_opens_containers(bool val) {
open_containers = val;
}
void set_using_text_gumps(bool val) {
using_text_gumps = val;
}
bool is_using_text_gumps() const {
return (using_text_gumps || is_new_style());
}
bool is_roof_mode() const {
return roof_mode;
}
void set_roof_mode(bool val) {
roof_mode = val;
}
bool using_hackmove();
void set_hackmove(bool hackmove);
uint8 is_dragging_enabled() {
return dragging_enabled;
}
void set_dragging_enabled(bool drag) {
dragging_enabled = drag;
}
bool is_god_mode_enabled() const {
return (god_mode_enabled && cheats_enabled);
}
bool toggle_god_mode() {
return (god_mode_enabled = !god_mode_enabled);
}
bool are_cheats_enabled() const {
return cheats_enabled;
}
void set_cheats_enabled(bool cheat) {
cheats_enabled = cheat;
}
bool has_unlimited_casting() const {
return (unlimited_casting && cheats_enabled);
}
void set_unlimited_casting(bool unlimited) {
unlimited_casting = unlimited;
}
bool is_armageddon() const {
return armageddon;
}
void set_armageddon(bool val) {
armageddon = val;
}
bool is_ethereal() const {
return ethereal;
}
void set_ethereal(bool val) {
ethereal = val;
}
ConverseGumpType get_converse_gump_type() const {
return converse_gump_type;
}
void set_converse_gump_type(ConverseGumpType new_type);
bool using_new_converse_gump();
void set_free_balloon_movement(bool val) {
free_balloon_movement = val;
}
bool has_free_balloon_movement() const {
return free_balloon_movement;
}
bool is_forcing_solid_converse_bg() const {
return force_solid_converse_bg;
}
uint16 get_converse_gump_width() const {
return converse_gump_width;
}
uint16 get_min_converse_gump_width() const {
return min_converse_gump_width;
}
uint16 get_game_width() const {
return game_width;
}
uint16 get_game_height() const {
return game_height;
}
uint16 get_game_x_offset() const {
return game_x_offset;
}
uint16 get_game_y_offset() const {
return game_y_offset;
}
Common::Path get_data_file_path(const Common::Path &datafile);
/* Return instances of Game classes */
static Game *get_game() {
return game;
}
Configuration *get_config() {
return config;
}
Script *get_script() {
return script;
}
Screen *get_screen() {
return screen;
}
Background *get_background() {
return background;
}
GamePalette *get_palette() {
return palette;
}
Dither *get_dither() {
return dither;
}
FontManager *get_font_manager() {
return font_manager;
}
TileManager *get_tile_manager() {
return tile_manager;
}
ObjManager *get_obj_manager() {
return obj_manager;
}
ActorManager *get_actor_manager() {
return actor_manager;
}
EggManager *get_egg_manager() {
return egg_manager;
}
Magic *get_magic() {
return magic;
}
Map *get_game_map() {
return game_map;
}
MapWindow *get_map_window() {
return map_window;
}
MsgScroll *get_scroll() {
return scroll;
}
Player *get_player() {
return player;
}
Party *get_party() {
return party;
}
Converse *get_converse() {
return converse;
}
ConverseGump *get_converse_gump() {
return conv_gump;
}
ViewManager *get_view_manager() {
return view_manager;
}
GameClock *get_clock() {
return _clock;
}
Portrait *get_portrait() {
return portrait;
}
UseCode *get_usecode() {
return usecode;
}
Events *get_event() {
return event;
}
GUI *get_gui() {
return gui;
}
SoundManager *get_sound_manager() {
return sound_manager;
}
Cursor *get_cursor() {
return cursor;
}
EffectManager *get_effect_manager() {
return effect_manager;
}
CommandBar *get_command_bar() {
return command_bar;
}
CommandBar *get_new_command_bar() {
return new_command_bar;
}
Weather *get_weather() {
return weather;
}
Book *get_book() {
return book;
}
KeyBinder *get_keybinder() {
return keybinder;
}
protected:
void init_converse();
void init_converse_gump_settings();
private:
void update_once(bool process_gui_input, bool run_converse);
};
extern uint getRandom(uint maxVal);
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,356 @@
/* 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/conf/configuration.h"
#include "ultima/nuvie/files/nuvie_io_file.h"
#include "ultima/nuvie/core/game.h"
#include "ultima/nuvie/save/obj_list.h"
#include "ultima/nuvie/core/weather.h"
#include "ultima/nuvie/core/game_clock.h"
namespace Ultima {
namespace Nuvie {
GameClock::GameClock(nuvie_game_t type) : game_type(type), day_of_week(0) {
date_string[10] = '\0';
time_string[10] = '\0';
init();
}
GameClock::~GameClock() {
}
void GameClock::init() {
move_counter = 0;
time_counter = 0;
// tick_counter = 0;
minute = 0;
hour = 0;
day = 0;
month = 0;
year = 0;
rest_counter = 0;
//active = true;
num_timers = 0;
}
bool GameClock::load(NuvieIO *objlist) {
init();
if (game_type == NUVIE_GAME_U6) {
objlist->seek(OBJLIST_OFFSET_U6_GAMETIME); // start of time data
} else {
objlist->seek(OBJLIST_OFFSET_WOU_GAMETIME); // start of time data
}
minute = objlist->read1();
hour = objlist->read1();
day = objlist->read1();
month = objlist->read1();
year = objlist->read2();
update_day_of_week();
if (game_type == NUVIE_GAME_U6) {
load_U6_timers(objlist);
} else if (game_type == NUVIE_GAME_MD) {
load_MD_timers(objlist);
}
DEBUG(0, LEVEL_INFORMATIONAL, "Loaded game clock: %s %s\n", get_date_string(), get_time_string());
return true;
}
void GameClock::load_U6_timers(NuvieIO *objlist) {
num_timers = GAMECLOCK_NUM_TIMERS;
timers.reserve(num_timers);
timers.clear();
objlist->seek(OBJLIST_OFFSET_U6_TIMERS);
for (uint8 i = 0; i < GAMECLOCK_NUM_TIMERS; i++) {
timers.push_back(objlist->read1());
}
objlist->seek(OBJLIST_OFFSET_U6_REST_COUNTER);
rest_counter = objlist->read1();
}
void GameClock::load_MD_timers(NuvieIO *objlist) {
num_timers = GAMECLOCK_NUM_TIMERS * 3 + 1; //three berries per party member. 16 slots + 1 for the blue berry counter.
timers.reserve(num_timers);
timers.clear();
objlist->seek(OBJLIST_OFFSET_MD_BERRY_TIMERS);
for (uint8 i = 0; i < GAMECLOCK_NUM_TIMERS; i++) {
uint8 byte = objlist->read1();
timers.push_back((uint8)(byte & 0xf)); //purple
timers.push_back((uint8)(byte >> 4)); //green
timers.push_back((uint8)(objlist->read1() & 0xf)); //brown
}
objlist->seek(OBJLIST_OFFSET_MD_BLUE_BERRY_COUNTER);
timers.push_back(objlist->read1()); //blue berry counter
}
bool GameClock::save(NuvieIO *objlist) {
objlist->seek(OBJLIST_OFFSET_U6_GAMETIME); // start of time data
objlist->write1(minute);
objlist->write1(hour);
objlist->write1(day);
objlist->write1(month);
objlist->write2(year);
if (game_type == NUVIE_GAME_U6) {
save_U6_timers(objlist);
} else if (game_type == NUVIE_GAME_MD) {
save_MD_timers(objlist);
}
return true;
}
void GameClock::save_U6_timers(NuvieIO *objlist) {
objlist->seek(OBJLIST_OFFSET_U6_TIMERS);
for (int i = 0; i < num_timers; i++) {
objlist->write1(timers[i]);
}
objlist->seek(OBJLIST_OFFSET_U6_REST_COUNTER);
objlist->write1(rest_counter);
}
void GameClock::save_MD_timers(NuvieIO *objlist) {
objlist->seek(OBJLIST_OFFSET_MD_BERRY_TIMERS);
for (int i = 0; i < num_timers - 1; i += 3) {
objlist->write1((uint8)(timers[i + 1] << 4) + timers[i]);
objlist->write1(timers[i + 2]);
}
objlist->seek(OBJLIST_OFFSET_MD_BLUE_BERRY_COUNTER);
objlist->write1(timers[num_timers - 1]);
}
void GameClock::inc_move_counter() {
move_counter++;
/* if((move_counter % GAMECLOCK_TICKS_PER_MINUTE) == 0)
inc_minute();
else
tick_counter++;*/ // commented out because time is updated independently
return;
}
// move_counter by a minute.
void GameClock::inc_move_counter_by_a_minute() {
move_counter += GAMECLOCK_TICKS_PER_MINUTE;
/* inc_minute();*/ // commented out because time is updated independently
}
// advance game time to the start of the next hour.
void GameClock::advance_to_next_hour() {
minute = 0;
inc_hour();
}
void GameClock::inc_minute(uint16 amount) {
minute += amount;
if (minute >= 60) {
for (; minute >= 60; minute -= 60) {
inc_hour();
}
time_counter += minute;
DEBUG(0, LEVEL_INFORMATIONAL, "%s\n", get_time_string());
} else {
time_counter += amount;
}
//update_timers(1);
return;
}
void GameClock::inc_hour() {
if (rest_counter > 0)
rest_counter--;
if (hour == 23) {
hour = 0;
inc_day();
} else {
hour++;
time_counter += 60;
}
if (game_type == NUVIE_GAME_U6)
Game::get_game()->get_weather()->update_moongates();
return;
}
void GameClock::inc_day() {
if (day == 28) {
day = 1;
inc_month();
} else {
day++;
time_counter += 1440;
}
update_day_of_week();
DEBUG(0, LEVEL_INFORMATIONAL, "%s\n", get_date_string());
return;
}
void GameClock::inc_month() {
if (month == 12) {
month = 1;
inc_year();
} else {
month++;
time_counter += 40320;
}
return;
}
void GameClock::inc_year() {
year++;
time_counter += 483840;
return;
}
uint32 GameClock::get_move_count() const {
return move_counter;
}
const char *GameClock::get_time_of_day_string() {
if (hour < 12)
return "morning";
if (hour >= 12 && hour <= 18)
return "afternoon";
return "evening";
}
uint8 GameClock::get_hour() const {
return hour;
}
uint8 GameClock::get_minute() const {
return minute;
}
uint8 GameClock::get_day() const {
return day;
}
uint8 GameClock::get_month() const {
return month;
}
uint16 GameClock::get_year() const {
return year;
}
uint8 GameClock::get_day_of_week() const {
return day_of_week;
}
const char *GameClock::get_date_string() {
Common::sprintf_s(date_string, "%2u-%02u-%04u", month, day, year);
return date_string;
}
const char *GameClock::get_time_string() {
char c;
uint8 tmp_hour;
if (hour < 12)
c = 'A';
else
c = 'P';
if (hour > 12)
tmp_hour = hour - 12;
else {
if (hour == 0)
tmp_hour = 12;
else
tmp_hour = hour;
}
Common::sprintf_s(time_string, "%0u:%02u %c.M.", tmp_hour, minute, c);
return time_string;
}
uint8 GameClock::get_rest_counter() const {
return rest_counter;
}
inline void GameClock::update_day_of_week() {
day_of_week = day % 7;
if (day_of_week == 0)
day_of_week = 7;
}
void GameClock::set_timer(uint8 timer_num, uint8 val) {
if (timer_num < num_timers) {
timers[timer_num] = val;
}
}
uint8 GameClock::get_timer(uint8 timer_num) const {
if (timer_num < num_timers) {
return timers[timer_num];
}
return 0;
}
void GameClock::update_timers(uint8 amount) {
for (uint8 i = 0; i < num_timers; i++) {
if (timers[i] > amount) {
timers[i] -= amount;
} else
timers[i] = 0;
}
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,156 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_GAME_CLOCK_H
#define NUVIE_CORE_GAME_CLOCK_H
#include "ultima/shared/std/containers.h"
#include "ultima/nuvie/core/nuvie_defs.h"
namespace Ultima {
namespace Nuvie {
using Std::vector;
#define GAMECLOCK_TICKS_PER_MINUTE 4
#define GAMECLOCK_NUM_TIMERS 16
#define GAMECLOCK_TIMER_U6_LIGHT 0
#define GAMECLOCK_TIMER_U6_INFRAVISION 1
#define GAMECLOCK_TIMER_U6_STORM 13
#define GAMECLOCK_TIMER_U6_TIME_STOP 14
#define GAMECLOCK_TIMER_U6_ECLIPSE 15
#define GAMECLOCK_TIMER_MD_BLUE_BERRY 16*3
class Configuration;
class NuvieIO;
class GameClock {
nuvie_game_t game_type;
uint16 minute;
uint8 hour;
uint8 day;
uint8 month;
uint16 year;
uint8 day_of_week;
uint32 move_counter; // player steps taken since start
uint32 time_counter; // game minutes
// uint32 tick_counter; // moves/turns since last minute
char date_string[11];
char time_string[11];
//bool active; // clock is active and running (false = paused)
vector<uint8> timers;
uint8 num_timers;
uint8 rest_counter; //hours until the party will heal again while resting.
public:
GameClock(nuvie_game_t type);
~GameClock();
bool load(NuvieIO *objlist);
bool save(NuvieIO *objlist);
//void set_active(bool state) { active = state; }
//bool get_active() { return(active); }
void inc_move_counter();
void inc_move_counter_by_a_minute();
void advance_to_next_hour();
void inc_minute(uint16 amount = 1);
void inc_hour();
void inc_day();
void inc_month();
void inc_year();
uint32 get_move_count() const;
const char *get_time_of_day_string();
uint8 get_hour() const;
uint8 get_minute() const;
uint8 get_day() const;
uint8 get_month() const;
uint16 get_year() const;
uint8 get_day_of_week() const;
const char *get_date_string();
const char *get_time_string();
uint8 get_rest_counter() const;
void set_rest_counter(uint8 value) {
rest_counter = value;
}
uint32 get_ticks() const {
return SDL_GetTicks(); // milliseconds since start
}
uint32 get_game_ticks() const {
return time_counter/**GAMECLOCK_TICKS_PER_MINUTE+tick_counter*/;
}
// uint32 get_time() { return(time_counter); } // get_game_ticks() is preferred
uint32 get_turn() const {
return move_counter;
}
void set_timer(uint8 timer_num, uint8 val);
uint8 get_timer(uint8 timer_num) const;
void update_timers(uint8 amount);
//MD berry counters
uint8 get_purple_berry_counter(uint8 actor_num) const {
return get_timer(actor_num * 3);
}
uint8 get_green_berry_counter(uint8 actor_num) const {
return get_timer(actor_num * 3 + 1);
}
uint8 get_brown_berry_counter(uint8 actor_num) const {
return get_timer(actor_num * 3 + 2);
}
protected:
void init();
inline void update_day_of_week();
private:
void load_U6_timers(NuvieIO *objlist);
void load_MD_timers(NuvieIO *objlist);
void save_U6_timers(NuvieIO *objlist);
void save_MD_timers(NuvieIO *objlist);
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,179 @@
/* 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/conf/configuration.h"
#include "ultima/nuvie/files/nuvie_io_file.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/files/u6_lib_n.h"
#include "ultima/nuvie/files/u6_lzw.h"
#include "ultima/nuvie/core/look.h"
namespace Ultima {
namespace Nuvie {
Look::Look(const Configuration *cfg)
: look_data(nullptr), desc_buf(nullptr), config(cfg), max_len(0) {
look_tbl[2047] = nullptr;
}
Look::~Look() {
free(look_data);
free(desc_buf);
}
bool Look::init() {
Common::Path filename;
U6Lzw lzw;
uint32 decomp_size;
unsigned char *ptr;
const char *s;
uint16 i, j;
unsigned int len;
int game_type;
NuvieIOFileRead look_file;
config->value("config/GameType", game_type);
switch (game_type) {
case NUVIE_GAME_U6 :
config_get_path(config, "look.lzd", filename);
look_data = lzw.decompress_file(filename, decomp_size);
if (look_data == nullptr)
return false;
break;
case NUVIE_GAME_MD :
case NUVIE_GAME_SE :
U6Lib_n lib_file;
config_get_path(config, "look.lzc", filename);
if (lib_file.open(filename, 4, game_type) == false)
return false;
look_data = lib_file.get_item(0);
break;
}
ptr = look_data;
// i: current string pos, j: last string pos
for (i = 0, j = 0; i < 2048;) {
// get number of string
i = ptr[0] + (ptr[1] << 8);
if (i >= 2048)
break;
// store pointer to look_data buffer
look_tbl[i] = s = reinterpret_cast<char *>(&ptr[2]);
// update max_len
len = strlen(s);
if (max_len < len)
max_len = len;
ptr += len + 3;
// fill all empty strings between current and last one
for (; j <= i; j++)
look_tbl[j] = s;
}
// fill remaining strings with "Nothing"
for (i = j; i < 2048; i++) {
look_tbl[i] = look_tbl[0]; // nothing
}
// allocate space for description buffer
desc_buf = (char *)malloc(max_len + 1);
if (desc_buf == nullptr)
return false;
return true;
}
const char *Look::get_description(uint16 tile_num, bool *plural) {
const char *desc;
char c;
uint16 i, j;
uint16 len;
bool has_plural = false;
if (tile_num >= 2048)
return nullptr;
desc = look_tbl[tile_num];
len = strlen(desc);
for (i = 0, j = 0; i < len;) {
if (desc[i] == '\\' || desc[i] == '/') {
has_plural = true;
c = desc[i];
for (i++; Common::isAlpha(desc[i]) && i < len; i++) {
if ((*plural && c == '\\') || (!*plural && c == '/')) {
desc_buf[j] = desc[i];
j++;
}
}
} else {
desc_buf[j] = desc[i];
i++;
j++;
}
}
desc_buf[j] = desc[i];
*plural = has_plural; //we return if this string contained a plural form.
return desc_buf;
}
bool Look::has_plural(uint16 tile_num) const {
if (tile_num >= 2048)
return false;
const char *desc = look_tbl[tile_num];
if (desc == nullptr)
return false;
for (; *desc != '\0'; desc++) {
if (*desc == '\\')
return true;
}
return false;
}
uint16 Look::get_max_len() const {
return max_len;
}
void Look::print() {
for (int i = 0; i < 2048; i++) {
DEBUG(0, LEVEL_DEBUGGING, "%04d :: %s\n", i, look_tbl[i]);
}
return;
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,57 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_LOOKUP_H
#define NUVIE_CORE_LOOKUP_H
namespace Ultima {
namespace Nuvie {
class Configuration;
class Look {
const Configuration *config;
const char *look_tbl[2048];
uint16 max_len;
unsigned char *look_data;
char *desc_buf;
public:
Look(const Configuration *cfg);
~Look();
bool init();
// if description has a plural form, true is returned in plural
const char *get_description(uint16 tile_num, bool *plural);
bool has_plural(uint16 tile_num) const;
uint16 get_max_len() const;
void print();
protected:
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,502 @@
/* 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_misc.h"
#include "ultima/nuvie/core/party.h"
#include "ultima/nuvie/gui/widgets/msg_scroll.h"
#include "ultima/nuvie/conf/configuration.h"
#include "ultima/nuvie/files/nuvie_io_file.h"
#include "ultima/nuvie/usecode/usecode.h"
#include "ultima/nuvie/gui/gui.h"
#include "ultima/nuvie/gui/gui_yes_no_dialog.h"
#include "ultima/nuvie/gui/widgets/console.h"
#include "ultima/nuvie/actors/actor.h"
#include "ultima/nuvie/actors/actor_manager.h"
#include "ultima/nuvie/core/obj_manager.h"
#include "ultima/nuvie/views/view_manager.h"
#include "ultima/nuvie/views/spell_view.h"
#include "ultima/nuvie/sound/sound_manager.h"
#include "ultima/nuvie/core/u6_objects.h"
#include "ultima/nuvie/core/magic.h"
#include "ultima/nuvie/core/game.h"
#include "ultima/nuvie/core/game_clock.h"
#include "ultima/nuvie/misc/u6_llist.h"
#include "ultima/nuvie/core/effect.h"
#include "ultima/nuvie/core/weather.h"
#include "ultima/nuvie/script/script.h"
namespace Ultima {
namespace Nuvie {
/* Syllable Meaning Syllable Meaning
* An .......... Negate/Dispel Nox ................ Poison
* Bet ................. Small Ort ................. Magic
* Corp ................ Death Por ......... Move/Movement
* Des ............ Lower/Down Quas ............. Illusion
* Ex ................ Freedom Rel ................ Change
* Flam ................ Flame Sanct .. Protect/Protection
* Grav ......... Energy/Field Tym .................. Time
* Hur .................. Wind Uus .............. Raise/Up
* In ...... Make/Create/Cause Vas ................. Great
* Jux ...... Danger/Trap/Harm Wis ........ Know/Knowledge
* Kal ......... Summon/Invoke Xen .............. Creature
* Lor ................. Light Ylem ............... Matter
* Mani ......... Life/Healing Zu .................. Sleep
*/
const char *const syllable[26] = {"An ", "Bet ", "Corp ", "Des ", "Ex ", "Flam ", "Grav ", "Hur ", "In ", "Jux ", "Kal ", "Lor ", "Mani ", "Nox ", "Ort ", "Por ", "Quas ", "Rel ", "Sanct ", "Tym ", "Uus ", "Vas ", "Wis ", "Xen ", "Ylem ", "Zu "};
const char *const reagent[8] = {"mandrake root", "nightshade", "black pearl", "blood moss", "spider silk", "garlic", "ginseng", "sulfurous ash"}; // check names
const int obj_n_reagent[8] = {OBJ_U6_MANDRAKE_ROOT, OBJ_U6_NIGHTSHADE, OBJ_U6_BLACK_PEARL, OBJ_U6_BLOOD_MOSS, OBJ_U6_SPIDER_SILK, OBJ_U6_GARLIC, OBJ_U6_GINSENG, OBJ_U6_SULFUROUS_ASH};
Magic::Magic() : event(nullptr), magic_script(nullptr), spellbook_obj(nullptr), state(0) {
ARRAYCLEAR(spell);
clear_cast_buffer();
}
Magic::~Magic() {
for (int index = 0; index < 256; index++)
delete(spell[index]);
}
bool Magic::init(Events *evt) {
event = evt;
return read_spell_list();
}
bool Magic::read_spell_list() {
return Game::get_game()->get_script()->call_magic_get_spell_list(spell);
}
Obj *Magic::book_equipped() {
// book(s) equipped? Maybe should check all locations?
Actor *caster = event->player->get_actor();
Obj *obj = caster->inventory_get_readied_object(ACTOR_ARM);
if (obj && obj->obj_n == OBJ_U6_SPELLBOOK)
return obj;
obj = caster->inventory_get_readied_object(ACTOR_ARM_2);
if (obj && obj->obj_n == OBJ_U6_SPELLBOOK)
return obj;
return nullptr;
}
bool Magic::start_new_spell() {
spellbook_obj = book_equipped();
if (Game::get_game()->get_clock()->get_timer(GAMECLOCK_TIMER_U6_STORM) > 0 && !Game::get_game()->has_unlimited_casting()) {
event->scroll->display_string("No magic at this time!\n\n");
} else if (spellbook_obj != nullptr) {
state = MAGIC_STATE_SELECT_SPELL;
clear_cast_buffer();
event->close_gumps();
Game::get_game()->get_view_manager()->set_spell_mode(event->player->get_actor(), spellbook_obj);
Game::get_game()->get_view_manager()->get_spell_view()->grab_focus();
return true;
} else
event->scroll->display_string("\nNo spellbook is readied.\n\n");
state = MAGIC_STATE_READY;
return false;
}
bool Magic::cast() {
if (magic_script != nullptr)
return false;
Game::get_game()->get_view_manager()->close_spell_mode();
cast_buffer_str[cast_buffer_len] = '\0';
DEBUG(0, LEVEL_DEBUGGING, "Trying to cast '%s'\n", cast_buffer_str);
/* decode the invocation */
// FIXME? original allows random order of syllables, do we want that?
// easy enough to sort invocations, but would somewhat limit custom spells
uint16 index;
if (cast_buffer_len != 0) {
for (index = 0; index < 256; index++) {
if (spell[index] == nullptr) {
continue;
}
if (!strcmp(spell[index]->invocation, cast_buffer_str)) {
break;
}
}
} else {
sint16 view_spell = Game::get_game()->get_view_manager()->get_spell_view()->get_selected_spell();
if (view_spell < 256 && view_spell != -1)
index = (uint16)view_spell;
else
index = 256;
}
if (index >= 256) {
DEBUG(0, LEVEL_DEBUGGING, "didn't find spell in spell list\n");
event->scroll->display_string("\nThat spell is not in thy spellbook!\n");
return false;
}
//20110701 Pieter Luteijn: add an assert(spell[index]) to be sure it's not nullptr?
if (cast_buffer_len != 0) {
event->scroll->display_string("\n(");
event->scroll->display_string(spell[index]->name);
event->scroll->display_string(")\n");
} else {
event->scroll->display_string(spell[index]->name);
event->scroll->display_string("\n\"");
display_spell_incantation(index);
event->scroll->display_string("\"\n");
}
if (Game::get_game()->has_unlimited_casting()) {
cast_spell_directly(index);
return true;
}
/* debug block */
DEBUG(0, LEVEL_DEBUGGING, "matched spell #%d\n", index);
DEBUG(0, LEVEL_DEBUGGING, "name: %s\n", spell[index]->name);
DEBUG(0, LEVEL_DEBUGGING, "reagents: ");
const char *comma = "";
for (uint8 shift = 0; shift < 8; shift++) {
if (1 << shift & spell[index]->reagents) {
DEBUG(1, LEVEL_DEBUGGING, "%s%s", comma, reagent[shift]);
comma = ", ";
}
}
DEBUG(1, LEVEL_DEBUGGING, "\n");
//DEBUG(0,LEVEL_DEBUGGING,"script: %s\n",spell[index]->script);
/* end debug block */
if (Game::get_game()->user_paused()) //event->mode == WAIT_MODE)
return false;
// book(s) equipped? Maybe should check all locations?
Actor *caster = event->player->get_actor();
Obj *right = caster->inventory_get_readied_object(ACTOR_ARM);
Obj *left = caster->inventory_get_readied_object(ACTOR_ARM_2);
uint8 books = 0;
if (right != nullptr && right->obj_n == OBJ_U6_SPELLBOOK) {
books += 1;
};
if (left != nullptr && left->obj_n == OBJ_U6_SPELLBOOK) {
books += 2;
};
if (right && right->obj_n != OBJ_U6_SPELLBOOK)
right = nullptr;
if (left && left->obj_n != OBJ_U6_SPELLBOOK)
left = nullptr;
if (right == nullptr && left == nullptr) {
event->scroll->display_string("\nNo spellbook is readied.\n");
return false;
}
// any spells available?
uint32 spells = 0;
if ((books & 1) && right->container) { // hmm, relying on shortcut logic here.
spells = right->container->count();
}
if ((books & 2) && left->container) {
spells += left->container->count();
}
if (!spells) {
event->scroll->display_string("\nNo spells in the spellbook.\n");
return false;
}
// spell (or catch all spell 255) in (one of the) book(s)?
if (spellbook_has_spell(right, index) == false && spellbook_has_spell(left, index) == false) {
event->scroll->display_string("\nThat spell is not in thy spellbook!\n");
return false;
}
// level of caster sufficient
uint8 spell_level = MIN(8, (index / 16) + 1);
if (caster->get_level() < spell_level) {
event->scroll->display_string("\nYour level is not high enough.\n");
return false;
}
// enough Magic Points available
if (caster->get_magic() < spell_level) {
event->scroll->display_string("\nNot enough magic points.\n");
return false;
}
// reagents available
for (uint8 shift = 0; shift < 8; shift++) {
if (1 << shift & spell[index]->reagents) {
if (!caster->inventory_has_object(obj_n_reagent[shift], 0, false)) {
DEBUG(0, LEVEL_DEBUGGING, "Didn't have %s\n", reagent[shift]);
event->scroll->display_string("\nNo Reagents.\n");
Game::get_game()->get_sound_manager()->playSfx(NUVIE_SFX_FAILURE);
return false;
}
DEBUG(0, LEVEL_DEBUGGING, "Ok, has %s\n", reagent[shift]);
}
}
/* TODO check all pre-requisites before continue */
// 'spell failed' because of bad luck
// anything else?
// consume the reagents and magic points; we checked so they must be there.
caster->set_magic(caster->get_magic() - spell_level); // add a MAX (0, here?
for (int shift = 0; shift < 8; shift++) {
if (1 << shift & spell[index]->reagents) {
// FIXME Although we just checked, maybe something is messed up, so we
// should probably check that we're not passing nullptr to delete_obj
caster->inventory_del_object(obj_n_reagent[shift], 1, 0);
}
}
cast_spell_directly(index);
event->player->subtract_movement_points(spell_level * 3 + 10);
return true;
}
void Magic::display_spell_incantation(uint8 index) {
string incantation_str;
for (int i = 0; spell[index]->invocation[i] != '\0'; i++)
incantation_str += syllable[spell[index]->invocation[i] - Common::KEYCODE_a];
incantation_str.erase(incantation_str.size() - 1); // get rid of extra space at the end
event->scroll->display_string(incantation_str);
}
void Magic::show_spell_description(uint8 index) {
event->scroll->display_string(spell[index]->name);
event->scroll->display_string("-");
display_spell_incantation(index);
display_ingredients(index);
}
void Magic::display_ingredients(uint8 index) {
event->scroll->display_string("\nIngredients:\n");
if (spell[index]->reagents == 0) {
event->scroll->display_string("None\n\n");
return;
}
string list;
for (int shift = 0; shift < 8; shift++) {
if (1 << shift & spell[index]->reagents) {
list += " ";
list += reagent[shift];
list += "\n";
}
}
list += "\n";
event->scroll->set_discard_whitespace(false);
event->scroll->display_string(list);
event->scroll->set_discard_whitespace(true);
}
void Magic::cast_spell_directly(uint8 spell_num) {
string lua = "run_magic_script(\"";
lua += spell[spell_num]->invocation;
lua += "\")";
magic_script = Game::get_game()->get_script()->new_thread_from_string(lua.c_str());
if (magic_script)
process_script_return(magic_script->start());
}
bool Magic::resume(const MapCoord &location) {
if (magic_script) {
process_script_return(magic_script->resume_with_location(location));
}
return true;
}
bool Magic::resume(NuvieDir dir) {
if (magic_script) {
process_script_return(magic_script->resume_with_direction(dir));
}
return true;
}
bool Magic::resume_with_spell_num(uint8 spell_num) {
if (magic_script) {
process_script_return(magic_script->resume_with_spell_num(spell_num));
}
return true;
}
bool Magic::resume(Obj *obj) {
if (magic_script) {
process_script_return(magic_script->resume_with_obj(obj));
}
return true;
}
bool Magic::resume() {
if (magic_script) {
process_script_return(magic_script->resume_with_nil());
}
return true;
}
bool Magic::spellbook_has_spell(Obj *book, uint8 spell_index) {
if (!book)
return false;
if (book->find_in_container(OBJ_U6_SPELL, MAGIC_ALL_SPELLS, OBJ_MATCH_QUALITY) ||
book->find_in_container(OBJ_U6_SPELL, spell_index, OBJ_MATCH_QUALITY)) {
return true;
}
return false;
}
bool Magic::process_script_return(uint8 ret) {
Game::get_game()->get_view_manager()->close_all_gumps();
if (ret == NUVIE_SCRIPT_ERROR) {
delete magic_script;
magic_script = nullptr;
return false;
}
uint32 nturns;
uint8 *cb_msgid;
switch (ret) {
case NUVIE_SCRIPT_FINISHED :
delete magic_script;
magic_script = nullptr;
state = MAGIC_STATE_READY;
Game::get_game()->get_actor_manager()->startActors(); // end player turn
break;
case NUVIE_SCRIPT_GET_TARGET :
state = MAGIC_STATE_ACQUIRE_TARGET;
break;
case NUVIE_SCRIPT_GET_DIRECTION :
state = MAGIC_STATE_ACQUIRE_DIRECTION;
break;
case NUVIE_SCRIPT_GET_INV_OBJ :
state = MAGIC_STATE_ACQUIRE_INV_OBJ;
break;
case NUVIE_SCRIPT_GET_OBJ :
state = MAGIC_STATE_ACQUIRE_OBJ;
break;
case NUVIE_SCRIPT_GET_SPELL :
state = MAGIC_STATE_ACQUIRE_SPELL;
break;
case NUVIE_SCRIPT_ADVANCE_GAME_TIME :
nturns = magic_script->get_data();
DEBUG(0, LEVEL_DEBUGGING, "Magic: Advance %d turns\n", nturns);
cb_msgid = new uint8;
*cb_msgid = NUVIE_SCRIPT_CB_ADV_GAME_TIME;
new GameTimedCallback((CallBack *)this, cb_msgid, nturns);
break;
case NUVIE_SCRIPT_TALK_TO_ACTOR :
state = MAGIC_STATE_TALK_TO_ACTOR;
break;
default :
DEBUG(0, LEVEL_WARNING, "Unknown ScriptThread return code!\n");
break;
}
return true;
}
Actor *Magic::get_actor_from_script() {
if (magic_script && (state == MAGIC_STATE_ACQUIRE_INV_OBJ || state == MAGIC_STATE_TALK_TO_ACTOR))
return Game::get_game()->get_actor_manager()->get_actor((uint8)magic_script->get_data());
return nullptr;
}
uint16 Magic::callback(uint16 msg, CallBack *caller, void *data) {
if (msg == CB_DATA_READY) {
if (event->input.type != EVENTINPUT_KEY)
return 0;
Common::KeyCode sym = event->input.key;
if (state == MAGIC_STATE_SELECT_SPELL) {
if (sym >= Common::KEYCODE_a && sym <= Common::KEYCODE_z) {
if (cast_buffer_len < 4) {
cast_buffer_str[cast_buffer_len++] = sym;
event->scroll->display_string(syllable[sym - Common::KEYCODE_a]);
return 1; // handled the event
}
return 1; // handled the event
} else if (sym == Common::KEYCODE_BACKSPACE) {
if (cast_buffer_len > 0) {
cast_buffer_len--; // back up a syllable FIXME, doesn't handle automatically inserted newlines, so we need to keep track more. (THAT SHOULD BE DONE BY MSGSCROLL)
size_t len = strlen(syllable[cast_buffer_str[cast_buffer_len] - Common::KEYCODE_a]);
while (len--) event->scroll->remove_char();
event->scroll->Display(true);
return 1; // handled the event
}
return 1; // handled the event
}
} // MAGIC_STATE_SELECT_SPELL
if (state == MAGIC_STATE_ACQUIRE_TARGET) {
if (sym >= Common::KEYCODE_1 && sym <= Common::KEYCODE_9) {
cast();//event->player->get_party()->get_actor(sym - 48-1));
event->cancel_key_redirect();
return 1; // handled the event
}
}
// We must handle all keys even those we may not want or else
// we'll lose input focus, except for these three which end
// Casting. (besides, not handling all keys means they go back
// to global which could start another action)
if (event->input.action_key_type != DO_ACTION_KEY && event->input.action_key_type != CANCEL_ACTION_KEY)
return 1;
return 0;
} else if (magic_script) {
switch (msg) {
case NUVIE_SCRIPT_GET_TARGET :
process_script_return(magic_script->resume_with_location(MapCoord(200, 200, 0))); //FIXME need to get real loc.
break;
case NUVIE_SCRIPT_GET_DIRECTION :
process_script_return(magic_script->resume_with_direction(NUVIE_DIR_N)); //FIXME need to get real dir.
break;
}
}
return 1;
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,167 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_MAGIC_H
#define NUVIE_CORE_MAGIC_H
#include "ultima/nuvie/core/events.h"
#include "ultima/nuvie/misc/call_back.h"
namespace Ultima {
namespace Nuvie {
#define REAGENT_U6_MANDRAKE_ROOT 0x01
#define REAGENT_U6_NIGHTSHADE 0x02
#define REAGENT_U6_BLACK_PEARL 0x04
#define REAGENT_U6_BLOOD_MOSS 0x08
#define REAGENT_U6_SPIDER_SILK 0x10
#define REAGENT_U6_GARLIC 0x20
#define REAGENT_U6_GINSENG 0x40
#define REAGENT_U6_SULFUROUS_ASH 0x80
#define MAX_SCRIPT_LENGTH 65000
#define MAX_TOKEN_LENGTH 255
#define MAX_STACK_DEPTH 255
#define MAGIC_STATE_READY 0x00
#define MAGIC_STATE_SELECT_SPELL 0x01
#define MAGIC_STATE_PROCESS_SCRIPT 0x02
#define MAGIC_STATE_ACQUIRE_TARGET 0x03
#define MAGIC_STATE_ACQUIRE_INPUT 0x04
#define MAGIC_STATE_ACQUIRE_DIRECTION 0x05
#define MAGIC_STATE_ACQUIRE_INV_OBJ 0x06
#define MAGIC_STATE_TALK_TO_ACTOR 0x07
#define MAGIC_STATE_ACQUIRE_SPELL 0x08
#define MAGIC_STATE_ACQUIRE_OBJ 0x09
#define MAGIC_ALL_SPELLS 255
class ScriptThread;
class NuvieIOFileRead;
class Spell {
public: /* saves using dumb get / set functions */
uint8 num;
char *name; // eg. "Heal"
char *invocation; // eg. "im"
uint8 reagents; // reagents used
Spell(uint8 new_num, const char *new_name = "undefined name", const char *new_invocation = "kawo", uint8 new_reagents = 255) {
num = new_num;
name = scumm_strdup(new_name);
invocation = scumm_strdup(new_invocation);
reagents = new_reagents;
};
~Spell() {
free(name);
free(invocation);
}
};
class Magic : public CallBack {
private:
Spell *spell[256]; // spell list;
char cast_buffer_str[26]; // buffer for spell syllables typed.
uint8 cast_buffer_len; // how many characters typed in the spell buffer.
Events *event;
uint8 state;
ScriptThread *magic_script;
Obj *spellbook_obj;
/* TODO
* add a register array, or a pointer to a list of variables?
*/
public:
Magic();
~Magic() override;
bool init(Events *evt);
bool read_spell_list();
void clear_cast_buffer() {
cast_buffer_str[0] = '\0';
cast_buffer_len = 0;
}
bool start_new_spell();
Obj *book_equipped();
bool cast();
void cast_spell_directly(uint8 spell_num);
uint16 callback(uint16 msg, CallBack *caller, void *data = nullptr) override;
bool process_script_return(uint8 ret);
bool resume(const MapCoord &location);
bool resume(NuvieDir dir);
bool resume_with_spell_num(uint8 spell_num);
bool resume(Obj *obj);
bool resume();
bool is_waiting_for_location() const {
if (magic_script && state == MAGIC_STATE_ACQUIRE_TARGET) return true;
else return false;
}
bool is_waiting_for_direction() const {
if (magic_script && state == MAGIC_STATE_ACQUIRE_DIRECTION) return true;
else return false;
}
bool is_waiting_for_inventory_obj() const {
if (magic_script && state == MAGIC_STATE_ACQUIRE_INV_OBJ) return true;
else return false;
}
bool is_waiting_for_obj() const {
if (magic_script && state == MAGIC_STATE_ACQUIRE_OBJ) return true;
else return false;
}
bool is_waiting_to_talk() const {
if (state == MAGIC_STATE_TALK_TO_ACTOR) return true;
else return false;
}
bool is_waiting_for_spell() const {
if (magic_script && state == MAGIC_STATE_ACQUIRE_SPELL) return true;
else return false;
}
bool is_selecting_spell() const {
if (magic_script && state == MAGIC_STATE_SELECT_SPELL) return true;
else return false;
}
bool is_waiting_to_resume() const {
if (magic_script) return true;
else return false;
}
Spell *get_spell(uint8 spell_num) {
return spell[spell_num];
}
Obj *get_spellbook_obj() {
return spellbook_obj;
}
Actor *get_actor_from_script();
void show_spell_description(uint8 index);
private:
bool spellbook_has_spell(Obj *book, uint8 spell_index);
void display_ingredients(uint8 index);
void display_spell_incantation(uint8 index);
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,823 @@
/* 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/shared/std/string.h"
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/files/nuvie_io_file.h"
#include "ultima/nuvie/conf/configuration.h"
#include "ultima/nuvie/core/game.h"
#include "ultima/nuvie/core/tile_manager.h"
#include "ultima/nuvie/actors/actor_manager.h"
#include "ultima/nuvie/core/map.h"
#include "ultima/nuvie/gui/widgets/map_window.h"
#include "ultima/nuvie/misc/u6_misc.h"
namespace Ultima {
namespace Nuvie {
Map::Map(const Configuration *cfg) : config(cfg), tile_manager(nullptr),
obj_manager(nullptr), actor_manager(nullptr), surface(nullptr),
roof_surface(nullptr) {
ARRAYCLEAR(dungeons);
config->value(config_get_game_key(config) + "/roof_mode", roof_mode, false);
}
Map::~Map() {
if (surface == nullptr)
return;
free(surface);
for (int i = 0; i < 5; i++)
free(dungeons[i]);
if (roof_surface)
free(roof_surface);
}
byte *Map::get_map_data(uint8 level) {
if (level == 0)
return surface;
if (level > 5)
return nullptr;
return dungeons[level - 1];
}
uint16 *Map::get_roof_data(uint8 level) {
if (level == 0)
return roof_surface;
return nullptr;
}
const Tile *Map::get_tile(uint16 x, uint16 y, uint8 level, bool original_tile) {
if (level > 5)
return nullptr;
WRAP_COORD(x, level);
WRAP_COORD(y, level);
const uint8 *ptr = get_map_data(level);
const Tile *map_tile;
if (original_tile)
map_tile = tile_manager->get_original_tile(ptr[y * get_width(level) + x]);
else
map_tile = tile_manager->get_tile(ptr[y * get_width(level) + x]);
return map_tile;
}
uint16 Map::get_width(uint8 level) const {
if (level == 0)
return 1024; // surface
return 256; // dungeon
}
bool Map::is_passable(uint16 x, uint16 y, uint8 level) {
WRAP_COORD(x, level);
WRAP_COORD(y, level);
uint8 obj_status = obj_manager->is_passable(x, y, level);
if (obj_status == OBJ_NOT_PASSABLE) {
return false;
}
//special case for bridges, hacked doors and dungeon entrances etc.
if (obj_status != OBJ_NO_OBJ && obj_manager->is_forced_passable(x, y, level))
return true;
const uint8 *ptr = get_map_data(level);
const Tile *map_tile = tile_manager->get_original_tile(ptr[y * get_width(level) + x]);
return map_tile->passable;
}
/***
* Can we enter this map location by traveling in a given direction?
* Used by MD
*/
bool Map::is_passable(uint16 x, uint16 y, uint8 level, NuvieDir dir) {
if (is_passable_from_dir(x, y, level, get_reverse_direction(dir))) {
sint16 rel_x, rel_y;
uint16 tx, ty;
get_relative_dir(get_reverse_direction(dir), &rel_x, &rel_y);
tx = wrap_signed_coord((sint16)x + rel_x, level);
ty = wrap_signed_coord((sint16)y + rel_y, level);
return is_passable_from_dir(tx, ty, level, dir);
}
return false;
}
bool Map::is_passable_from_dir(uint16 x, uint16 y, uint8 level, NuvieDir dir) {
WRAP_COORD(x, level);
WRAP_COORD(y, level);
uint8 obj_status = obj_manager->is_passable(x, y, level);
if (obj_status == OBJ_NOT_PASSABLE) {
return false;
}
//special case for bridges, hacked doors and dungeon entrances etc.
if (obj_status != OBJ_NO_OBJ && obj_manager->is_forced_passable(x, y, level))
return true;
const uint8 *ptr = get_map_data(level);
const Tile *map_tile = tile_manager->get_original_tile(ptr[y * get_width(level) + x]);
if (!map_tile->passable && !(map_tile->flags1 & TILEFLAG_WALL)) {
switch (dir) {
case NUVIE_DIR_W :
return (map_tile->flags1 & TILEFLAG_WALL_WEST);
case NUVIE_DIR_S :
return (map_tile->flags1 & TILEFLAG_WALL_SOUTH);
case NUVIE_DIR_E :
return (map_tile->flags1 & TILEFLAG_WALL_EAST);
case NUVIE_DIR_N :
return (map_tile->flags1 & TILEFLAG_WALL_NORTH);
case NUVIE_DIR_NE :
return !(!(map_tile->flags1 & TILEFLAG_WALL_NORTH) || !(map_tile->flags1 & TILEFLAG_WALL_EAST));
case NUVIE_DIR_NW :
return !(!(map_tile->flags1 & TILEFLAG_WALL_NORTH) || !(map_tile->flags1 & TILEFLAG_WALL_WEST));
case NUVIE_DIR_SE :
return !(!(map_tile->flags1 & TILEFLAG_WALL_SOUTH) || !(map_tile->flags1 & TILEFLAG_WALL_EAST));
case NUVIE_DIR_SW :
return !(!(map_tile->flags1 & TILEFLAG_WALL_SOUTH) || !(map_tile->flags1 & TILEFLAG_WALL_WEST));
default:
error("Invalid direction in Map::is_passable_from_dir");
}
}
return map_tile->passable;
}
/* Returns true if an entire area is_passable(). */
bool Map::is_passable(uint16 x1, uint16 y1, uint16 x2, uint16 y2, uint8 level) {
for (int x = x1; x <= x2; x++)
for (int y = y1; y <= y2; y++)
if (!is_passable((uint16)x, (uint16)y, level))
return false;
return true;
}
bool Map::is_boundary(uint16 x, uint16 y, uint8 level) {
WRAP_COORD(x, level);
WRAP_COORD(y, level);
uint8 *ptr = get_map_data(level);
Tile *map_tile = tile_manager->get_tile(ptr[y * get_width(level) + x]);
if (map_tile->boundary && obj_manager->is_forced_passable(x, y, level) == false)
return true;
if (obj_manager->is_boundary(x, y, level))
return true;
return false;
}
bool Map::is_missile_boundary(uint16 x, uint16 y, uint8 level, Obj *excluded_obj) {
WRAP_COORD(x, level);
WRAP_COORD(y, level);
uint8 *ptr = get_map_data(level);
Tile *map_tile = tile_manager->get_tile(ptr[y * get_width(level) + x]);
if ((map_tile->flags2 & TILEFLAG_MISSILE_BOUNDARY) != 0 && obj_manager->is_forced_passable(x, y, level) == false)
return true;
if (obj_manager->is_boundary(x, y, level, TILEFLAG_MISSILE_BOUNDARY, excluded_obj))
return true;
return false;
}
bool Map::is_water(uint16 x, uint16 y, uint16 level, bool ignore_objects) {
WRAP_COORD(x, level);
WRAP_COORD(y, level);
if (!ignore_objects) {
const Obj *obj = obj_manager->get_obj(x, y, level);
if (obj != nullptr)
return false;
}
const uint8 *ptr = get_map_data(level);
const Tile *map_tile = tile_manager->get_original_tile(ptr[y * get_width(level) + x]);
if (map_tile->water)
return true;
return false;
}
bool Map::is_damaging(uint16 x, uint16 y, uint8 level, bool ignore_objects) {
const uint8 *ptr = get_map_data(level);
WRAP_COORD(x, level);
WRAP_COORD(y, level);
const Tile *map_tile = tile_manager->get_original_tile(ptr[y * get_width(level) + x]);
if (map_tile->damages)
return true;
if (!ignore_objects) {
if (obj_manager->is_damaging(x, y, level))
return true;
}
return false;
}
bool Map::can_put_obj(uint16 x, uint16 y, uint8 level) {
LineTestResult lt;
if (lineTest(x, y, x, y, level, LT_HitActors | LT_HitUnpassable, lt)) {
if (lt.hitObj) {
// We can place an object on a bench or table. Or on any other object if
// the object is passable and not on a boundary.
Tile *obj_tile = obj_manager->get_obj_tile(lt.hitObj->obj_n, lt.hitObj->frame_n);
if ((obj_tile->flags3 & TILEFLAG_CAN_PLACE_ONTOP) ||
(obj_tile->passable && !is_boundary(lt.hit_x, lt.hit_y, lt.hit_level))) {
return true;
} else {
return false;
}
}
}
if (is_missile_boundary(x, y, level))
return false;
return true;
}
uint8 Map::get_impedance(uint16 x, uint16 y, uint8 level, bool ignore_objects) {
uint8 *ptr = get_map_data(level);
WRAP_COORD(x, level);
WRAP_COORD(y, level);
const Tile *map_tile = tile_manager->get_original_tile(ptr[y * get_width(level) + x]);
uint8 impedance = 0;
if (!ignore_objects) {
const U6LList *obj_list = obj_manager->get_obj_list(x, y, level);
if (obj_list) {
for (const U6Link *link = obj_list->start(); link != nullptr; link = link->next) {
const Obj *obj = (Obj *)link->data;
if (obj != nullptr) {
uint8 tile_flag = obj_manager->get_obj_tile(obj->obj_n, obj->frame_n)->flags1;
if ((tile_flag & TILEFLAG_BLOCKING) == 0) {
impedance += (tile_flag & TILEFLAG_IMPEDANCE) >> TILEFLAG_IMPEDANCE_SHIFT;
}
}
}
}
}
if ((map_tile->flags1 & TILEFLAG_BLOCKING) == 0)
impedance += (map_tile->flags1 & TILEFLAG_IMPEDANCE) >> TILEFLAG_IMPEDANCE_SHIFT;
return impedance;
}
const Tile *Map::get_dmg_tile(uint16 x, uint16 y, uint8 level) {
const Tile *tile = get_tile(x, y, level);
if (tile->damages)
return tile;
return obj_manager->get_obj_dmg_tile(x, y, level);
}
bool Map::actor_at_location(uint16 x, uint16 y, uint8 level, bool inc_surrounding_objs) {
WRAP_COORD(x, level);
WRAP_COORD(y, level);
//check for blocking Actor at location.
if (actor_manager->get_actor(x, y, level, inc_surrounding_objs) != nullptr)
return true;
return false;
}
/* Return pointer to actor standing at map coordinates.
*/
Actor *Map::get_actor(uint16 x, uint16 y, uint8 z, bool inc_surrounding_objs) {
WRAP_COORD(x, z);
WRAP_COORD(y, z);
return (actor_manager->get_actor(x, y, z, inc_surrounding_objs));
}
const char *Map::look(uint16 x, uint16 y, uint8 level) {
unsigned char *ptr;
uint16 qty = 0;
if (level == 0) {
ptr = surface;
} else
ptr = dungeons[level - 1];
WRAP_COORD(x, level);
WRAP_COORD(y, level);
Obj *obj = obj_manager->get_obj(x, y, level);
if (obj != nullptr && !(obj->status & OBJ_STATUS_INVISIBLE) //only show visible objects.
&& !Game::get_game()->get_map_window()->tile_is_black(obj->x, obj->y, obj)) {
// tile = tile_manager->get_original_tile(obj_manager->get_obj_tile_num(obj->obj_n)+obj->frame_n);
// tile_num = tile->tile_num;
// qty = obj->qty;
return obj_manager->look_obj(obj);
}
uint16 tile_num = ptr[y * get_width(level) + x];
return tile_manager->lookAtTile(tile_num, qty, true);
}
bool Map::loadMap(TileManager *tm, ObjManager *om) {
Common::Path filename;
NuvieIOFileRead map_file;
NuvieIOFileRead chunks_file;
uint8 i;
tile_manager = tm;
obj_manager = om;
config_get_path(config, "map", filename);
if (map_file.open(filename) == false)
return false;
config_get_path(config, "chunks", filename);
if (chunks_file.open(filename) == false)
return false;
unsigned char *map_data = map_file.readAll();
if (map_data == nullptr)
return false;
unsigned char *chunk_data = chunks_file.readAll();
if (chunk_data == nullptr)
return false;
unsigned char *map_ptr = map_data;
surface = (unsigned char *)malloc(1024 * 1024);
if (surface == nullptr)
return false;
for (i = 0; i < 64; i++) {
insertSurfaceSuperChunk(map_ptr, chunk_data, i);
map_ptr += 384;
}
for (i = 0; i < 5; i++) {
dungeons[i] = (unsigned char *)malloc(256 * 256);
if (dungeons[i] == nullptr)
return false;
insertDungeonSuperChunk(map_ptr, chunk_data, i);
map_ptr += 1536;
}
free(map_data);
free(chunk_data);
if (roof_mode)
loadRoofData();
/* ERIC Useful for testing map wrapping
I plan to add a map patch function
to allow custom map changes to be
loaded easily into nuvie.
uint16 mx,my;
for(my=100;my<130;my++)
for(mx=0;mx<30;mx++)
surface[my * 1024 + mx] = 1;
for(my=100;my<130;my++)
for(mx=1000;mx<1024;mx++)
surface[my * 1024 + mx] = 111;
*/
/*
printf("\n\n\n\n\n\n\n");
uint16 mx,my;
for(my=0;my<1024;my++)
{
for(mx=0;mx<1024;mx++)
{
printf("%3d,", surface[my * 1024 + mx]+1);
}
printf("\n");
}
*/
return true;
}
bool Map::has_roof(uint16 x, uint16 y, uint8 level) const {
if (!roof_mode || level != 0)
return false;
if (roof_surface[y * 1024 + x] != 0)
return true;
return false;
}
Common::Path Map::getRoofDataFilename() const {
Std::string game_type, tmp;
Common::Path datadir, path, mapfile;
config->value("config/datadir", tmp, "");
config->value("config/GameID", game_type);
datadir = Common::Path(tmp);
build_path(datadir, "maps", path);
datadir = path;
build_path(datadir, game_type, path);
datadir = path;
build_path(datadir, "roof_map_00.dat", mapfile);
return mapfile;
}
Common::Path Map::getRoofTilesetFilename() const {
Std::string tmp;
Common::Path datadir;
Common::Path imagefile;
Common::Path path;
config->value("config/datadir", tmp, "");
datadir = Common::Path(tmp);
build_path(datadir, "images", path);
datadir = path;
build_path(datadir, "roof_tiles.bmp", imagefile);
return imagefile;
}
void Map::set_roof_mode(bool roofs) {
roof_mode = roofs;
if (roof_mode) {
if (roof_surface)
return;
else
loadRoofData();
} else {
if (roof_surface) {
free(roof_surface);
roof_surface = nullptr;
}
}
}
void Map::loadRoofData() {
NuvieIOFileRead file;
roof_surface = (uint16 *)malloc(1024 * 1024 * 2);
if (file.open(getRoofDataFilename())) {
memset(roof_surface, 0, 1024 * 1024 * 2);
uint16 *ptr = roof_surface;
while (!file.is_eof()) {
uint16 offset = file.read2();
ptr += offset;
uint8 run_len = file.read1();
for (uint8 i = 0; i < run_len; i++) {
*ptr = file.read2();
ptr++;
}
}
} else {
if (roof_surface) {
free(roof_surface);
roof_surface = nullptr;
}
roof_mode = false;
}
}
void Map::saveRoofData() {
NuvieIOFileWrite file;
uint32 prev_offset = 0;
uint32 cur_offset = 0;
uint16 run_length = 0;
if (roof_surface && file.open(getRoofDataFilename())) {
for (; cur_offset < 1048576;) {
for (; cur_offset < prev_offset + 65535 && cur_offset < 1048576;) {
if (roof_surface[cur_offset] != 0) {
file.write2((uint16)(cur_offset - prev_offset));
for (run_length = 0; run_length < 256; run_length++) {
if (roof_surface[cur_offset + run_length] == 0)
break;
}
if (run_length == 256)
run_length--;
file.write1((uint8)run_length);
for (uint8 i = 0; i < run_length; i++) {
file.write2(roof_surface[cur_offset + i]);
}
cur_offset += run_length;
break;
}
cur_offset++;
if (cur_offset == prev_offset + 65535) {
//write blank.
file.write2(65535);
file.write1(0);
}
}
prev_offset = cur_offset;
}
}
}
void Map::insertSurfaceSuperChunk(const unsigned char *schunk, const unsigned char *chunk_data, uint8 schunk_num) {
uint16 world_x, world_y;
uint16 c1, c2;
uint8 i, j;
world_x = schunk_num % 8;
world_y = (schunk_num - world_x) / 8;
world_x *= 128;
world_y *= 128;
for (i = 0; i < 16; i++) {
for (j = 0; j < 16; j += 2) {
c1 = ((schunk[1] & 0xf) << 8) | schunk[0];
c2 = (schunk[2] << 4) | (schunk[1] >> 4);
insertSurfaceChunk(&chunk_data[c1 * 64], world_x + j * 8, world_y + i * 8);
insertSurfaceChunk(&chunk_data[c2 * 64], world_x + (j + 1) * 8, world_y + i * 8);
schunk += 3;
}
}
}
void Map::insertSurfaceChunk(const unsigned char *chunk, uint16 x, uint16 y) {
unsigned char *map_ptr;
map_ptr = &surface[y * 1024 + x];
for (int i = 0; i < 8; i++) {
memcpy(map_ptr, chunk, 8);
map_ptr += 1024;
chunk += 8;
}
}
void Map::insertDungeonSuperChunk(const unsigned char *schunk, const unsigned char *chunk_data, uint8 level) {
uint16 c1, c2;
uint8 i, j;
for (i = 0; i < 32; i++) {
for (j = 0; j < 32; j += 2) {
c1 = ((schunk[1] & 0xf) << 8) | schunk[0];
c2 = (schunk[2] << 4) | (schunk[1] >> 4);
insertDungeonChunk(&chunk_data[c1 * 64], j * 8, i * 8, level);
insertDungeonChunk(&chunk_data[c2 * 64], (j + 1) * 8, i * 8, level);
schunk += 3;
}
}
}
void Map::insertDungeonChunk(const unsigned char *chunk, uint16 x, uint16 y, uint8 level) {
unsigned char *map_ptr;
map_ptr = &dungeons[level][y * 256 + x];
for (int i = 0; i < 8; i++) {
memcpy(map_ptr, chunk, 8);
map_ptr += 256;
chunk += 8;
}
}
/* Get absolute coordinates for relative destination from MapCoord.
*/
MapCoord MapCoord::abs_coords(sint16 dx, sint16 dy) const {
// uint16 pitch = Map::get_width(z); cannot call function without object
uint16 pitch = (z == 0) ? 1024 : 256;
dx += x;
dy += y;
// wrap on map boundary for MD
if (dx < 0)
dx = pitch + dx;
else if (dx >= pitch)
dx = pitch - dx;
if (dy < 0)
dy = 0;
else if (dy >= pitch)
dy = pitch - 1;
return (MapCoord(dx, dy, z));
}
/* Returns true if this map coordinate is visible in the game window.
*/
bool MapCoord::is_visible() const {
return (Game::get_game()->get_map_window()->in_window(x, y, z));
}
bool Map::testIntersection(int x, int y, uint8 level, uint8 flags, LineTestResult &Result, Obj *excluded_obj) {
/* more checks added, may need more testing (SB-X) */
#if 0
if (flags & LT_HitUnpassable) {
if (!is_passable(x, y, level)) {
Result.init(x, y, level, nullptr, obj_manager->get_obj(x, y, level, true));
return true;
}
}
if (flags & LT_HitForcedPassable) {
if (obj_manager->is_forced_passable(x, y, level)) {
Result.init(x, y, level, nullptr, obj_manager->get_obj(x, y, level, true));
return true;
}
}
if (flags & LT_HitActors) {
// TODO:
}
return false;
#else
if (flags & LT_HitUnpassable) {
if (!is_passable(x, y, level)) {
Obj *obj_hit = obj_manager->get_obj(x, y, level);
if (!obj_hit || !excluded_obj || obj_hit != excluded_obj) {
Result.init(x, y, level, nullptr, obj_manager->get_obj(x, y, level, true));
return true;
}
}
}
if (flags & LT_HitMissileBoundary) {
if (is_missile_boundary(x, y, level, excluded_obj)) {
Result.init(x, y, level, nullptr, obj_manager->get_obj(x, y, level, true));
return true;
}
}
if (flags & LT_HitForcedPassable) {
if (obj_manager->is_forced_passable(x, y, level)) {
Result.init(x, y, level, nullptr, obj_manager->get_obj(x, y, level, true));
return true;
}
}
if (flags & LT_HitActors) {
if (actor_manager->get_actor(x, y, level)) {
Result.init(x, y, level, actor_manager->get_actor(x, y, level), nullptr);
return true;
}
}
if ((flags & LT_HitLocation) && Result.loc_to_hit) {
if (x == Result.loc_to_hit->x && y == Result.loc_to_hit->y) {
Result.init(x, y, level, nullptr, nullptr);
Result.loc_to_hit->z = level;
Result.hitLoc = Result.loc_to_hit;
return true;
}
}
if (flags & LT_HitObjects) {
if (obj_manager->get_obj(x, y, level)) {
Result.init(x, y, level, nullptr, obj_manager->get_obj(x, y, level, true));
return true;
}
}
return false;
#endif
}
// returns true if a line hits something travelling from (start_x, start_y) to
// (end_x, end_y). If a hit occurs Result is filled in with the relevant info.
// If want_screen_space is true input tile coordinates are multiplied by 16 for
// line calculation and scaled back down before testing for collisions. The
// original game does this for projectiles.
bool Map::lineTest(int start_x, int start_y, int end_x, int end_y, uint8 level,
uint8 flags, LineTestResult &Result, uint32 skip, Obj *excluded_obj,
bool want_screen_space) {
// standard Bresenham's algorithm.
uint8 scale_factor_log2 = 0;
if (want_screen_space)
scale_factor_log2 = 4; // set scale factor to 16
int deltax = abs(end_x - start_x) << scale_factor_log2;
int deltay = abs(end_y - start_y) << scale_factor_log2;
int x = (start_x << scale_factor_log2);
int y = (start_y << scale_factor_log2);
x += ((1 << scale_factor_log2) >> 1); // start at the center of the tile
y += ((1 << scale_factor_log2) >> 1); // when in screen space
int d;
int xinc1, xinc2;
int yinc1, yinc2;
int dinc1, dinc2;
uint32 count;
int xtile = start_x;
int ytile = start_y;
int xtile_prev = xtile;
int ytile_prev = ytile;
if (deltax >= deltay) {
d = (deltay << 1) - deltax;
count = deltax + 1;
dinc1 = deltay << 1;
dinc2 = (deltay - deltax) << 1;
xinc1 = 1;
xinc2 = 1;
yinc1 = 0;
yinc2 = 1;
} else {
d = (deltax << 1) - deltay;
count = deltay + 1;
dinc1 = deltax << 1;
dinc2 = (deltax - deltay) << 1;
xinc1 = 0;
xinc2 = 1;
yinc1 = 1;
yinc2 = 1;
}
if (start_x > end_x) {
xinc1 = -xinc1;
xinc2 = -xinc2;
}
if (start_y > end_y) {
yinc1 = -yinc1;
yinc2 = -yinc2;
}
for (uint32 i = 0; i < count; i++) {
// only test for collision if tile coordinates have changed
if ((scale_factor_log2 == 0 || x >> scale_factor_log2 != xtile || y >> scale_factor_log2 != ytile)) {
xtile_prev = xtile;
ytile_prev = ytile;
xtile = x >> scale_factor_log2; // scale back down to tile
ytile = y >> scale_factor_log2; // space if necessary
// test the current location
if ((i >= skip) && (testIntersection(xtile, ytile, level, flags, Result, excluded_obj) == true)) {
Result.pre_hit_x = xtile_prev;
Result.pre_hit_y = ytile_prev;
return true;
}
}
if (d < 0) {
d += dinc1;
x += xinc1;
y += yinc1;
} else {
d += dinc2;
x += xinc2;
y += yinc2;
}
}
return false;
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,215 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_MAP_H
#define NUVIE_CORE_MAP_H
#include "ultima/shared/std/string.h"
#include "ultima/nuvie/core/obj_manager.h"
namespace Ultima {
namespace Nuvie {
class Configuration;
class U6LList;
class Actor;
class ActorManager;
class MapCoord;
class TileManager;
class Screen;
#define MAP_ORIGINAL_TILE true
enum LineTestFlags {
LT_HitActors = (1 << 0),
LT_HitUnpassable = (1 << 1),
LT_HitForcedPassable = (1 << 2),
LT_HitLocation = (1 << 3), /* hit location in Result */
LT_HitObjects = (1 << 4), /* hit any object */
LT_HitMissileBoundary = (1 << 5)
};
class LineTestResult {
public:
LineTestResult() { // clears properties not set by init() (SB-X)
hit_x = 0;
hit_y = 0;
hit_level = 0;
hitActor = nullptr;
hitObj = nullptr;
hitLoc = nullptr;
loc_to_hit = nullptr;
}
void init(int x, int y, uint8 level, Actor *actorHit, Obj *objHit) {
hit_x = x;
hit_y = y;
hit_level = level;
hitActor = actorHit;
hitObj = objHit;
}
int hit_x; // x coord where object / actor was hit
int hit_y; // y coord where object / actor was hit
int pre_hit_x;
int pre_hit_y;
uint8 hit_level; // map level where object / actor was hit
Actor *hitActor;
Obj *hitObj;
MapCoord *hitLoc;
MapCoord *loc_to_hit; // set hitLoc if hit x,y (z changes)
};
//typedef (*LineTestFilter)(int x, int y, int level, LineTestResult &Result);
/* Map Location with 2D X,Y coordinates and plane (map number)
*/
class MapCoord {
public:
union {
uint16 x;
sint16 sx;
};
union {
uint16 y;
sint16 sy;
};
uint8 z; // plane
MapCoord(uint16 nx, uint16 ny, uint16 nz = 0) {
x = nx;
y = ny;
z = nz;
}
MapCoord(Obj *obj) {
x = obj->x;
y = obj->y;
z = obj->z;
}
MapCoord() : x(0), y(0), z(0) { }
uint32 xdistance(const MapCoord &c2) const {
uint32 dist = abs(c2.x - x);
if (dist > 512)
dist = 1024 - dist;
return dist;
}
uint32 ydistance(const MapCoord &c2) const {
return abs(c2.y - y);
}
// greatest 2D distance X or Y (estimate of shortest)
uint32 distance(const MapCoord &c2) const {
uint16 dx = xdistance(c2), dy = ydistance(c2);
return (dx >= dy ? dx : dy);
}
// get absolute coordinates for relative destination (dx,dy)
MapCoord abs_coords(sint16 dx, sint16 dy) const;
// location is on screen?
bool is_visible() const;
void print_d(DebugLevelType level) const {
DEBUG(1, level, "%d, %d, %d", x, y, z);
}
void print_h(DebugLevelType level) const {
DEBUG(1, level, "%x, %x, %x", x, y, z);
}
void print_s(DebugLevelType level) const {
DEBUG(1, level, "%d, %d", sx, sy);
}
bool operator==(const MapCoord &c2) const {
return (x == c2.x && y == c2.y && z == c2.z);
}
bool operator!=(const MapCoord &c2) const {
return (!(*this == c2));
}
// MapCoord operator+(MapCoord &c2) { return(abs_coords(c2)); }
};
class Map {
const Configuration *config;
TileManager *tile_manager;
ObjManager *obj_manager;
ActorManager *actor_manager;
uint8 *surface;
uint8 *dungeons[5];
bool roof_mode;
uint16 *roof_surface;
public:
Map(const Configuration *cfg);
~Map();
void set_actor_manager(ActorManager *am) {
actor_manager = am;
}
Actor *get_actor(uint16 x, uint16 y, uint8 z, bool inc_surrounding_objs = true);
bool loadMap(TileManager *tm, ObjManager *om);
byte *get_map_data(uint8 level);
uint16 *get_roof_data(uint8 level);
const Tile *get_tile(uint16 x, uint16 y, uint8 level, bool original_tile = false);
uint16 get_width(uint8 level) const;
bool is_passable(uint16 x, uint16 y, uint8 level);
bool is_water(uint16 x, uint16 y, uint16 level, bool ignore_objects = false);
bool is_boundary(uint16 x, uint16 y, uint8 level);
bool is_missile_boundary(uint16 x, uint16 y, uint8 level, Obj *excluded_obj = nullptr);
bool is_damaging(uint16 x, uint16 y, uint8 level, bool ignore_objects = false);
bool can_put_obj(uint16 x, uint16 y, uint8 level);
bool actor_at_location(uint16 x, uint16 y, uint8 level, bool inc_surrounding_objs = true);
uint8 get_impedance(uint16 x, uint16 y, uint8 level, bool ignore_objects = false);
const Tile *get_dmg_tile(uint16 x, uint16 y, uint8 level);
bool is_passable(uint16 x, uint16 y, uint8 level, NuvieDir dir);
bool is_passable(uint16 x1, uint16 y1, uint16 x2, uint16 y2, uint8 level);
bool is_passable_from_dir(uint16 x, uint16 y, uint8 level, NuvieDir dir);
bool has_roof(uint16 x, uint16 y, uint8 level) const;
void set_roof_mode(bool roofs);
const char *look(uint16 x, uint16 y, uint8 level);
bool lineTest(int start_x, int start_y, int end_x, int end_y, uint8 level,
uint8 flags, LineTestResult &Result, uint32 skip = 0, Obj *excluded_obj = nullptr, bool want_screen_space = false); // excluded_obj only works for LT_HitUnpassable
bool testIntersection(int x, int y, uint8 level, uint8 flags, LineTestResult &Result, Obj *excluded_obj = nullptr); // excluded_obj only works for LT_HitUnpassable
void saveRoofData();
Common::Path getRoofTilesetFilename() const;
protected:
Common::Path getRoofDataFilename() const;
void insertSurfaceSuperChunk(const unsigned char *schunk_ptr, const unsigned char *chunk_data, uint8 schunk_num);
void insertSurfaceChunk(const unsigned char *chunk, uint16 x, uint16 y);
void insertDungeonSuperChunk(const unsigned char *schunk_ptr, const unsigned char *chunk_data, uint8 level);
void insertDungeonChunk(const unsigned char *chunk, uint16 x, uint16 y, uint8 level);
void loadRoofData();
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,41 @@
/* 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"
namespace Ultima {
namespace Nuvie {
#ifndef WITHOUT_DEBUG
void u6debug(bool no_header, const DebugLevelType level, const char *format, ...) {
va_list va;
va_start(va, format);
Common::String output = Common::String::vformat(format, va);
va_end(va);
::debugN(level, "%s", output.c_str());
}
#endif
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,139 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_NUVIE_DEFS_H
#define NUVIE_CORE_NUVIE_DEFS_H
#include "common/scummsys.h"
#include "ultima/nuvie/misc/sdl_compat.h"
namespace Ultima {
namespace Nuvie {
typedef int8 sint8;
typedef int16 sint16;
typedef int32 sint32;
#define USE_BUTTON Events::BUTTON_LEFT
#define WALK_BUTTON Events::BUTTON_RIGHT
#define ACTION_BUTTON Events::BUTTON_RIGHT
#define DRAG_BUTTON Events::BUTTON_LEFT
typedef uint8 nuvie_game_t; // Game type (1=u6,2=md,4=se)
#define NUVIE_GAME_NONE 0
#define NUVIE_GAME_U6 1
#define NUVIE_GAME_MD 2
#define NUVIE_GAME_SE 4
#define NUVIE_CONFIG_NAME_U6 "ultima6"
#define NUVIE_CONFIG_NAME_MD "martian"
#define NUVIE_CONFIG_NAME_SE "savage"
#define NUVIE_STYLE_ORIG 0
#define NUVIE_STYLE_NEW 1
#define NUVIE_STYLE_ORIG_PLUS_CUTOFF_MAP 2
#define NUVIE_STYLE_ORIG_PLUS_FULL_MAP 3
#define clamp_min(v, c) (((v) < (c)) ? (c) : (v))
#define clamp_max(v, c) (((v) > (c)) ? (c) : (v))
#define clamp(v, c1, c2) ( ((v) < (c1)) ? (c1) : (((v) > (c2)) ? (c2) : (v)) )
#ifndef INT_MAX
#define INT_MAX 0x7fffffff
#endif
#ifndef UCHAR_MAX
#define UCHAR_MAX 0xff
#endif
#ifndef SHRT_MAX
#define SHRT_MAX 0x7fff
#endif
//FIXME fix for future maps which will probably be 1024 wide starting at level 6..
#define WRAPPED_COORD(c,level) ((c)&((level)?255:1023))
#define WRAP_COORD(c,level) ((c)&=((level)?255:1023))
#define MAP_SIDE_LENGTH(map_level) ((map_level > 0 && map_level < 6) ? 256 : 1024)
/*
* on all levels, except level 0 (conveniently 'false') the map pitch is 256.
* to properly wrap, mask the coordinate with the relevant bit-mask.
* Another way to write this would be:
const uint16 map_pitch[2] = { 1024, 256 }; // width of 0:surface plane, and 1:all other planes
#define WRAPPED_COORD(c,level) ((c)&(map_pitch[(level==0)?0:1]-1)) // mask high bit, wrap C to map_pitch
#define WRAP_COORD(c,level) ((c)&=(map_pitch[(level==0)?0:1]-1)) // modifies C
*/
enum NuvieDir {
NUVIE_DIR_N = 0,
NUVIE_DIR_E = 1,
NUVIE_DIR_S = 2,
NUVIE_DIR_W = 3,
NUVIE_DIR_NE = 4,
NUVIE_DIR_SE = 5,
NUVIE_DIR_SW = 6,
NUVIE_DIR_NW = 7,
NUVIE_DIR_NONE = 8,
};
#define TRAMMEL_PHASE 1.75
#define FELUCCA_PHASE 1.1666666666666667
enum DebugLevelType {
LEVEL_EMERGENCY = 0,
LEVEL_ALERT,
LEVEL_CRITICAL,
LEVEL_ERROR,
LEVEL_WARNING,
LEVEL_NOTIFICATION,
LEVEL_INFORMATIONAL,
LEVEL_DEBUGGING
};
enum ConverseGumpType {
CONVERSE_GUMP_DEFAULT = 0,
CONVERSE_GUMP_U7_STYLE = 1,
CONVERSE_GUMP_WOU_STYLE = 2,
};
#ifdef WITHOUT_DEBUG
inline void u6debug(bool no_header, const DebugLevelType level, const char *format, ...) {}
#else
extern void u6debug(bool no_header, const DebugLevelType level, const char *format, ...);
#endif
#ifdef DEBUG
#undef DEBUG
#endif
#define DEBUG u6debug
#define U6PATH_DELIMITER '/'
#define NUVIE_RAND_MAX 0x7fffffff // POSIX: 2^(31)-1
#define NUVIE_RAND() getRandom(NUVIE_RAND_MAX)
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,326 @@
/* 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/core/obj_manager.h"
#include "ultima/nuvie/core/game.h"
#include "ultima/nuvie/core/u6_objects.h"
namespace Ultima {
namespace Nuvie {
Obj::Obj() : obj_n(0), status(0), nuvie_status(0), frame_n(0), qty(0),
quality(0), parent(nullptr), container(nullptr), x(0), y(0), z(0) {
}
Obj::Obj(Obj *sobj) {
memcpy(this, sobj, sizeof(Obj));
parent = nullptr;
container = nullptr;
}
void Obj::make_container() {
if (container == nullptr)
container = new U6LList();
return;
}
Obj *Obj::get_container_obj(bool recursive) {
Obj *obj = (is_in_container() ? (Obj *)parent : nullptr);
if (recursive) {
while (obj && obj->is_in_container())
obj = (Obj *)obj->parent;
}
return obj;
}
void Obj::set_on_map(U6LList *map_list) {
parent = map_list;
nuvie_status &= NUVIE_OBJ_STATUS_LOC_MASK_SET;
nuvie_status |= OBJ_LOC_MAP;
return;
}
void Obj::set_in_container(Obj *container_obj) {
parent = (void *)container_obj;
nuvie_status &= NUVIE_OBJ_STATUS_LOC_MASK_SET;
nuvie_status |= OBJ_LOC_CONT;
return;
}
void Obj::set_invisible(bool flag) {
if (flag)
status |= OBJ_STATUS_INVISIBLE;
else if (is_invisible())
status ^= OBJ_STATUS_INVISIBLE;
return;
}
void Obj::set_temporary(bool flag) {
if (flag)
status |= OBJ_STATUS_TEMPORARY;
else if (is_temporary())
status ^= OBJ_STATUS_TEMPORARY;
return;
}
void Obj::set_ok_to_take(bool flag, bool recursive) {
if (flag)
status |= OBJ_STATUS_OK_TO_TAKE;
else if (is_ok_to_take())
status ^= OBJ_STATUS_OK_TO_TAKE;
if (recursive && container) {
for (U6Link *link = container->start(); link != nullptr; link = link->next) {
Obj *obj = (Obj *)link->data;
obj->set_ok_to_take(flag, recursive);
}
}
}
void Obj::set_in_inventory() {
nuvie_status &= NUVIE_OBJ_STATUS_LOC_MASK_SET;
nuvie_status |= OBJ_LOC_INV;
return;
}
void Obj::readied() { //set_readied() ??
nuvie_status &= NUVIE_OBJ_STATUS_LOC_MASK_SET;
nuvie_status |= OBJ_LOC_READIED;
return;
}
void Obj::set_noloc() {
parent = nullptr;
nuvie_status &= NUVIE_OBJ_STATUS_LOC_MASK_SET; //clear location bits 0 = no loc
return;
}
void Obj::set_in_script(bool flag) {
if (flag)
nuvie_status |= NUVIE_OBJ_STATUS_SCRIPTING;
else if (is_script_obj())
nuvie_status ^= NUVIE_OBJ_STATUS_SCRIPTING;
return;
}
void Obj::set_actor_obj(bool flag) {
if (flag)
nuvie_status |= NUVIE_OBJ_STATUS_ACTOR_OBJ;
else if (is_actor_obj())
nuvie_status ^= NUVIE_OBJ_STATUS_ACTOR_OBJ;
return;
}
/* Returns true if an object is in an actor inventory, including containers and readied items. */
bool Obj::is_in_inventory(bool check_parent) const {
switch (get_engine_loc()) {
case OBJ_LOC_INV :
case OBJ_LOC_READIED :
return true;
case OBJ_LOC_CONT :
if (check_parent)
return ((Obj *)parent)->is_in_inventory(check_parent);
break;
default :
break;
}
return false;
}
uint8 Obj::get_engine_loc() const {
return (nuvie_status & NUVIE_OBJ_STATUS_LOC_MASK_GET);
}
Actor *Obj::get_actor_holding_obj() {
switch (get_engine_loc()) {
case OBJ_LOC_INV :
case OBJ_LOC_READIED :
return (Actor *)this->parent;
case OBJ_LOC_CONT :
return ((Obj *)parent)->get_actor_holding_obj();
default :
break;
}
return nullptr;
}
//Add child object into container, stacking if required
void Obj::add(Obj *obj, bool stack, bool addAtTail) {
if (container == nullptr)
make_container();
if (stack && Game::get_game()->get_obj_manager()->is_stackable(obj))
add_and_stack(obj, addAtTail);
else
if (!addAtTail)
container->addAtPos(0, obj);
else
container->add(obj);
obj->set_in_container(this);
return;
}
void Obj::add_and_stack(Obj *obj, bool addAtTail) {
U6Link *link;
Obj *cont_obj;
//should we recurse through nested containers?
for (link = container->start(); link != nullptr;) {
cont_obj = (Obj *)link->data;
link = link->next;
//match on obj_n, frame_n and quality.
if (obj->obj_n == cont_obj->obj_n && obj->frame_n == cont_obj->frame_n && obj->quality == cont_obj->quality) {
obj->qty += cont_obj->qty;
container->replace(cont_obj, obj); //replace cont_obj with obj in container list. should we do this to link->data directly?
delete_obj(cont_obj);
return;
}
}
if (!addAtTail)
container->addAtPos(0, obj); // add the object as we couldn't find another object to stack with.
else
container->add(obj);
return;
}
//Remove child object from container.
bool Obj::remove(Obj *obj) {
if (container == nullptr)
return false;
if (container->remove(obj) == false)
return false;
if (Game::get_game()->get_game_type() == NUVIE_GAME_SE) {
if (obj_n == OBJ_SE_JAR)
frame_n = 0; // empty jar frame
}
obj->x = 0;
obj->y = 0;
obj->z = 0;
obj->set_noloc();
return true;
}
Obj *Obj::find_in_container(uint16 objN, uint8 quality_, bool match_quality, uint8 frameN, bool match_frame_n, Obj **prev_obj) const {
U6Link *link;
Obj *obj;
if (container == nullptr)
return nullptr;
for (link = container->start(); link != nullptr; link = link->next) {
obj = (Obj *)link->data;
if (obj) {
if (obj->obj_n == objN && (match_quality == false || obj->quality == quality_) && (match_frame_n == false || obj->frame_n == frameN)) {
if (prev_obj != nullptr && obj == *prev_obj)
prev_obj = nullptr;
else {
if (prev_obj == nullptr || *prev_obj == nullptr)
return obj;
}
}
if (obj->container) {
obj = obj->find_in_container(objN, quality_, match_quality, frameN, match_frame_n, prev_obj);
if (obj)
return obj;
}
}
}
return nullptr;
}
uint32 Obj::get_total_qty(uint16 match_obj_n) {
U6Link *link;
Obj *obj;
uint16 total_qty = 0;
if (obj_n == match_obj_n) {
if (qty == 0)
total_qty += 1;
else
total_qty += qty;
}
if (container != nullptr) {
for (link = container->start(); link != nullptr; link = link->next) {
obj = (Obj *)link->data;
if (obj) {
if (obj->container)
total_qty += obj->get_total_qty(match_obj_n);
else if (obj->obj_n == match_obj_n) {
if (obj->qty == 0)
total_qty += 1;
else
total_qty += obj->qty;
}
}
}
}
return total_qty;
}
uint32 Obj::container_count_objects() const {
uint32 count = 0;
U6Link *link;
if (container != nullptr) {
for (link = container->start(); link != nullptr; link = link->next) {
++count;
}
}
return count;
}
bool Obj::is_ok_to_take() const {
return ((status & OBJ_STATUS_OK_TO_TAKE) || Game::get_game()->using_hackmove());
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,193 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_OBJ_H
#define NUVIE_CORE_OBJ_H
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/misc/u6_llist.h"
namespace Ultima {
namespace Nuvie {
class Actor;
#define NO_OBJ_STATUS 0
// obj status bit flags
#define OBJ_STATUS_OK_TO_TAKE 0x1
//#define OBJ_STATUS_SEEN_EGG 0x2 // something to do with eggs <- not sure about this one.
#define OBJ_STATUS_INVISIBLE 0x2 // I think this is correct
#define OBJ_STATUS_CHARMED 0x4 // objlist.txt says 'charmed'
// position: A 2 bit field, so can't use plain | to check / |= to set these.
// FIXME: check to make sure we don't do this anywhere anymore
#define OBJ_STATUS_ON_MAP 0x0
#define OBJ_STATUS_IN_CONTAINER 0x8
#define OBJ_STATUS_IN_INVENTORY 0x10
#define OBJ_STATUS_READIED 0x18
#define OBJ_STATUS_MASK_GET 0x18
#define OBJ_STATUS_MASK_SET 0xE7
#define OBJ_STATUS_TEMPORARY 0x20
#define OBJ_STATUS_EGG_ACTIVE 0x40 // something to do with eggs
#define OBJ_STATUS_BROKEN 0x40
#define OBJ_STATUS_MUTANT 0x40
#define OBJ_STATUS_CURSED 0x40
#define OBJ_STATUS_LIT 0x80
//first 3 bits of nuvie_status code object location
//in the nuvie engine.
//Nuvie engine obj locations
#define OBJ_LOC_NONE 0
#define OBJ_LOC_INV 1
#define OBJ_LOC_MAP 2
#define OBJ_LOC_READIED 3
#define OBJ_LOC_CONT 4
#define NUVIE_OBJ_STATUS_LOC_MASK_GET 0x7
#define NUVIE_OBJ_STATUS_LOC_MASK_SET 0xf8
#define NUVIE_OBJ_STATUS_SCRIPTING 0x8
#define NUVIE_OBJ_STATUS_ACTOR_OBJ 0x10
#define OBJ_MATCH_QUALITY true
#define OBJ_NOMATCH_QUALITY false
#define OBJ_MATCH_FRAME_N true
#define OBJ_NOMATCH_FRAME_N false
//We use this in Obj::is_in_inventory()
#define OBJ_DONT_CHECK_PARENT false
class Obj {
uint8 nuvie_status;
public:
//uint16 objblk_n;
uint16 obj_n;
uint8 frame_n;
uint8 status;
uint16 x;
uint16 y;
uint8 z;
uint16 qty;
uint8 quality;
void *parent; //either an Obj pointer or an Actor pointer depending on engine_loc.
U6LList *container;
public:
Obj();
Obj(Obj *sobj);
bool is_script_obj() const {
return (nuvie_status & NUVIE_OBJ_STATUS_SCRIPTING);
}
bool is_actor_obj() const {
return (nuvie_status & NUVIE_OBJ_STATUS_ACTOR_OBJ);
}
bool is_ok_to_take() const;
bool is_invisible() const {
return (status & OBJ_STATUS_INVISIBLE);
}
bool is_charmed() const {
return (status & OBJ_STATUS_CHARMED);
}
bool is_temporary() const {
return (status & OBJ_STATUS_TEMPORARY);
}
bool is_egg_active() const {
return (status & OBJ_STATUS_EGG_ACTIVE);
}
bool is_broken() const {
return (status & OBJ_STATUS_BROKEN);
}
bool is_mutant() const {
return (status & OBJ_STATUS_MUTANT);
}
bool is_cursed() const {
return (status & OBJ_STATUS_CURSED);
}
bool is_lit() const {
return (status & OBJ_STATUS_LIT);
}
bool is_on_map() const {
return ((nuvie_status & NUVIE_OBJ_STATUS_LOC_MASK_GET) == OBJ_LOC_MAP);
}
bool is_in_container() const {
return ((nuvie_status & NUVIE_OBJ_STATUS_LOC_MASK_GET) == OBJ_LOC_CONT);
}
bool is_in_inventory(bool check_parent = true) const;
bool is_readied() const {
return ((nuvie_status & NUVIE_OBJ_STATUS_LOC_MASK_GET) == OBJ_LOC_READIED);
}
bool has_container() const {
return (container != nullptr);
}
void make_container();
Obj *get_container_obj(bool recursive = false);
uint32 container_count_objects() const;
uint8 get_engine_loc() const;
Actor *get_actor_holding_obj();
void set_on_map(U6LList *map_list);
void set_in_container(Obj *container_obj);
void set_in_inventory();
void set_invisible(bool flag);
void set_temporary(bool flag = true);
void set_ok_to_take(bool flag, bool recursive = false);
void readied();
void set_noloc();
void set_in_script(bool flag);
void set_actor_obj(bool flag);
void add(Obj *obj, bool stack = false, bool addAtTail = false);
bool remove(Obj *obj);
Obj *find_in_container(uint16 obj_n, uint8 quality, bool match_quality = OBJ_MATCH_QUALITY, uint8 frame_n = 0, bool match_frame_n = OBJ_NOMATCH_FRAME_N, Obj **prev_obj = nullptr) const;
uint32 get_total_qty(uint16 match_obj_n);
protected:
void add_and_stack(Obj *obj, bool addAtTail = false);
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,251 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_OBJ_MANAGER_H
#define NUVIE_CORE_OBJ_MANAGER_H
#include "ultima/shared/std/containers.h"
#include "ultima/nuvie/misc/iavl_tree.h"
#include "ultima/nuvie/core/tile_manager.h"
#include "ultima/nuvie/misc/u6_llist.h"
#include "ultima/nuvie/core/obj.h"
namespace Ultima {
namespace Nuvie {
//class U6LList;
class Configuration;
class EggManager;
class UseCode;
class NuvieIO;
class MapCoord;
class Actor;
//is_passable return codes
#define OBJ_NO_OBJ 0
#define OBJ_NOT_PASSABLE 1
#define OBJ_PASSABLE 2
#define OBJ_WEIGHT_INCLUDE_CONTAINER_ITEMS true
#define OBJ_WEIGHT_EXCLUDE_CONTAINER_ITEMS false
#define OBJ_WEIGHT_DO_SCALE true
#define OBJ_WEIGHT_DONT_SCALE false
#define OBJ_WEIGHT_EXCLUDE_QTY false
#define OBJ_ADD_TOP true
#define OBJ_SHOW_PREFIX true
#define OBJ_TEMP_INIT 255 // this is used to stop temporary objects from being cleaned upon startup.
#define OBJ_SEARCH_TOP true
#define OBJ_INCLUDE_IGNORED true
#define OBJ_EXCLUDE_IGNORED false
struct ObjTreeNode {
iAVLKey key;
U6LList *obj_list;
};
Obj *new_obj(uint16 obj_n, uint8 frame_n, uint16 x, uint16 y, uint16 z);
void delete_obj(Obj *obj);
void clean_obj_tree_node(void *node);
class ObjManager {
const Configuration *config;
int game_type;
EggManager *egg_manager;
TileManager *tile_manager;
//chunk object trees.
iAVLTree *surface[64];
iAVLTree *dungeon[5];
uint16 obj_to_tile[1024]; //maps object number (index) to tile number.
uint8 obj_weight[1024];
uint8 obj_stackable[1024];
U6LList *actor_inventories[256];
bool show_eggs;
uint16 egg_tile_num;
UseCode *usecode;
Std::list<Obj *> temp_obj_list;
Std::list<Obj *> tile_obj_list; // SE single instance 'map tile' objects
uint16 last_obj_blk_x, last_obj_blk_y;
uint8 last_obj_blk_z;
uint16 obj_save_count;
bool custom_actor_tiles;
public:
ObjManager(const Configuration *cfg, TileManager *tm, EggManager *em);
~ObjManager();
bool use_custom_actor_tiles() {
return custom_actor_tiles;
}
bool is_showing_eggs() {
return show_eggs;
}
void set_show_eggs(bool value) {
show_eggs = value;
}
bool loadObjs();
bool load_super_chunk(NuvieIO *chunk_buf, uint8 level, uint8 chunk_offset);
void startObjs();
void clean();
void clean_actor_inventories();
bool save_super_chunk(NuvieIO *save_buf, uint8 level, uint8 chunk_offset);
bool save_eggs(NuvieIO *save_buf);
bool save_inventories(NuvieIO *save_buf);
bool save_obj(NuvieIO *save_buf, Obj *obj, uint16 parent_objblk_n);
void set_usecode(UseCode *uc) {
usecode = uc;
}
UseCode *get_usecode() {
return usecode;
}
EggManager *get_egg_manager() {
return egg_manager;
}
//U6LList *get_obj_superchunk(uint16 x, uint16 y, uint8 level);
bool is_boundary(uint16 x, uint16 y, uint8 level, uint8 boundary_type = TILEFLAG_BOUNDARY, Obj *excluded_obj = nullptr);
//bool is_door(Obj * obj);
bool is_damaging(uint16 x, uint16 y, uint8 level);
uint8 is_passable(uint16 x, uint16 y, uint8 level);
bool is_forced_passable(uint16 x, uint16 y, uint8 level);
bool is_stackable(const Obj *obj) const;
bool is_breakable(const Obj *obj);
bool can_store_obj(const Obj *target, Obj *src) const; // Bag, open chest, spellbook.
bool can_get_obj(Obj *obj) const;
bool has_reduced_weight(uint16 obj_n) const;
bool has_reduced_weight(const Obj *obj) const {
return has_reduced_weight(obj->obj_n);
}
bool has_toptile(const Obj *obj) const;
bool obj_is_damaging(const Obj *obj, Actor *actor = nullptr); // if actor, it will damage and display text
bool is_door(uint16 x, uint16 y, uint8 level);
U6LList *get_obj_list(uint16 x, uint16 y, uint8 level) const;
Tile *get_obj_tile(uint16 obj_n, uint8 frame_n);
const Tile *get_obj_tile(uint16 x, uint16 y, uint8 level, bool top_obj = true);
const Tile *get_obj_dmg_tile(uint16 x, uint16 y, uint8 level);
Obj *get_obj(uint16 x, uint16 y, uint8 level, bool top_obj = OBJ_SEARCH_TOP, bool include_ignored_objects = OBJ_EXCLUDE_IGNORED, Obj *excluded_obj = nullptr);
Obj *get_obj_of_type_from_location_inc_multi_tile(uint16 obj_n, uint16 x, uint16 y, uint8 z);
Obj *get_obj_of_type_from_location_inc_multi_tile(uint16 obj_n, sint16 quality, sint32 qty, uint16 x, uint16 y, uint8 z);
Obj *get_obj_of_type_from_location(uint16 obj_n, uint16 x, uint16 y, uint8 z);
Obj *get_obj_of_type_from_location(uint16 obj_n, sint16 quality, sint32 qty, uint16 x, uint16 y, uint8 z);
Obj *get_objBasedAt(uint16 x, uint16 y, uint8 level, bool top_obj, bool include_ignored_objects = true, Obj *excluded_obj = nullptr);
Obj *get_tile_obj(uint16 obj_n);
uint16 get_obj_tile_num(uint16 obj_num) const;
inline bool is_corpse(const Obj *obj) const;
uint16 get_obj_tile_num(const Obj *obj) const;
void set_obj_tile_num(uint16 obj_num, uint16 tile_num);
U6LList *get_actor_inventory(uint16 actor_num);
bool actor_has_inventory(uint16 actor_num);
Obj *find_next_obj(uint8 level, Obj *prev_obj, bool match_frame_n = OBJ_NOMATCH_FRAME_N, bool match_quality = OBJ_MATCH_QUALITY);
Obj *find_obj(uint8 level, uint16 obj_n, uint8 quality, bool match_quality = OBJ_MATCH_QUALITY, uint16 frame_n = 0, bool match_frame_n = OBJ_NOMATCH_FRAME_N, Obj **prev_obj = nullptr);
bool move(Obj *obj, uint16 x, uint16 y, uint8 level);
bool add_obj(Obj *obj, bool addOnTop = false);
bool remove_obj_from_map(Obj *obj);
bool remove_obj_type_from_location(uint16 obj_n, uint16 x, uint16 y, uint8 z);
Obj *copy_obj(const Obj *obj);
const char *look_obj(Obj *obj, bool show_prefix = false);
Obj *get_obj_from_stack(Obj *obj, uint32 count);
bool list_add_obj(U6LList *list, Obj *obj, bool stack_objects = true, uint32 pos = 0);
const char *get_obj_name(Obj *obj);
const char *get_obj_name(uint16 obj_n);
const char *get_obj_name(uint16 obj_n, uint8 frame_n);
float get_obj_weight(const Obj *obj, bool include_container_items = OBJ_WEIGHT_INCLUDE_CONTAINER_ITEMS, bool scale = true, bool include_qty = true) const;
uint8 get_obj_weight_unscaled(uint16 obj_n) const {
return obj_weight[obj_n];
}
float get_obj_weight(uint16 obj_n) const;
void animate_forwards(Obj *obj, uint32 loop_count = 1);
void animate_backwards(Obj *obj, uint32 loop_count = 1);
void update(uint16 x, uint16 y, uint8 z, bool teleport = false);
bool unlink_from_engine(Obj *obj, bool run_usecode = true);
bool moveto_map(Obj *obj, MapCoord location);
bool moveto_inventory(Obj *obj, uint16 actor_num);
bool moveto_inventory(Obj *obj, Actor *actor);
bool moveto_container(Obj *obj, Obj *container_obj, bool stack = true);
protected:
void remove_obj(Obj *obj);
bool load_basetile();
bool load_weight_table();
bool addObjToContainer(U6LList *list, Obj *obj);
Obj *loadObj(NuvieIO *buf);
iAVLTree *get_obj_tree(uint16 x, uint16 y, uint8 level) const;
iAVLKey get_obj_tree_key(Obj *obj) const;
iAVLKey get_obj_tree_key(uint16 x, uint16 y, uint8 level) const;
//inline U6LList *ObjManager::get_schunk_list(uint16 x, uint16 y, uint8 level);
bool temp_obj_list_add(Obj *obj);
bool temp_obj_list_remove(Obj *obj);
void temp_obj_list_clean_level(uint8 z);
void temp_obj_list_clean_area(uint16 x, uint16 y);
void remove_temp_obj(Obj *tmp_obj);
inline Obj *find_obj_in_tree(uint16 obj_n, uint8 quality, bool match_quality, uint8 frame_n, bool match_frame_n, Obj **prev_obj, iAVLTree *obj_tree);
inline void start_obj_usecode(iAVLTree *obj_tree);
inline void print_egg_tree(iAVLTree *obj_tree);
public:
void print_object_list();
void print_egg_list();
void print_obj(const Obj *obj, bool in_container, uint8 indent = 0);
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,999 @@
/* 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_misc.h"
#include "ultima/nuvie/files/nuvie_io.h"
#include "ultima/nuvie/core/game.h"
#include "ultima/nuvie/core/converse.h"
#include "ultima/nuvie/core/timed_event.h"
#include "ultima/nuvie/conf/configuration.h"
#include "ultima/nuvie/actors/actor_manager.h"
#include "ultima/nuvie/sound/sound_manager.h"
#include "ultima/nuvie/views/view_manager.h"
#include "ultima/nuvie/core/player.h"
#include "ultima/nuvie/core/map.h"
#include "ultima/nuvie/gui/widgets/map_window.h"
#include "ultima/nuvie/usecode/u6_usecode.h"
#include "ultima/nuvie/gui/widgets/command_bar.h"
#include "ultima/nuvie/pathfinder/party_path_finder.h"
#include "ultima/nuvie/core/party.h"
#include "ultima/nuvie/views/view.h"
#include "ultima/nuvie/save/obj_list.h"
#include "ultima/nuvie/core/events.h"
namespace Ultima {
namespace Nuvie {
Party::Party(const Configuration *cfg) : config(cfg), game(nullptr),
actor_manager(nullptr), map(nullptr), pathfinder(nullptr),
rest_campfire(nullptr), formation(PARTY_FORM_STANDARD),
num_in_party(0), prev_leader_x(0), prev_leader_y(0),
defer_removing_dead_members(false), autowalk(false),
in_vehicle(false), in_combat_mode(false), lightsources(0),
combat_changes_music(false), vehicles_change_music(false) {
memset(&member, 0, sizeof member);
}
Party::~Party() {
if (pathfinder)
delete pathfinder;
}
bool Party::init(Game *g, ActorManager *am) {
Std::string formation_string;
game = g;
actor_manager = am;
map = g->get_game_map();
if (!pathfinder)
pathfinder = new PartyPathFinder(this);
autowalk = false;
in_vehicle = false;
config->value("config/general/party_formation", formation_string, "");
if (formation_string == "row")
formation = PARTY_FORM_ROW;
else if (formation_string == "column")
formation = PARTY_FORM_COLUMN;
else if (formation_string == "delta")
formation = PARTY_FORM_DELTA;
else
formation = PARTY_FORM_STANDARD;
config->value("config/audio/combat_changes_music", combat_changes_music, true);
config->value("config/audio/vehicles_change_music", vehicles_change_music, true);
return true;
}
bool Party::load(NuvieIO *objlist) {
uint8 actor_num;
uint16 i;
autowalk = false;
in_vehicle = false;
objlist->seek(OBJLIST_OFFSET_NUM_IN_PARTY);
num_in_party = objlist->read1();
objlist->seek(OBJLIST_OFFSET_PARTY_NAMES);
for (i = 0; i < num_in_party; i++) {
objlist->readToBuf((unsigned char *)member[i].name, PARTY_NAME_MAX_LENGTH + 1); // read in Player name.
}
objlist->seek(OBJLIST_OFFSET_PARTY_ROSTER);
for (i = 0; i < num_in_party; i++) {
actor_num = objlist->read1();
member[i].actor = actor_manager->get_actor(actor_num);
member[i].actor->set_in_party(true);
//member[i].inactive = false; // false unless actor is asleep, or paralyzed (is_immobile)
}
objlist->seek(OBJLIST_OFFSET_U6_COMBAT_MODE); // combat mode flag. NOTE! this offset is probably U6 specifix FIXME
in_combat_mode = (bool)objlist->read1();
MapCoord leader_loc = get_leader_location(); // previous leader location
prev_leader_x = leader_loc.x;
prev_leader_y = leader_loc.y;
reform_party();
autowalk = false;
if (actor_manager->get_actor(ACTOR_VEHICLE_ID_N)->get_worktype() == ACTOR_WT_PLAYER) { // WT_U6_PLAYER
set_in_vehicle(true);
hide();
}
for (i = 0; i < PARTY_MAX_MEMBERS; i++) {
clear_combat_target(i);
}
update_light_sources();
update_music();
return true;
}
bool Party::save(NuvieIO *objlist) {
uint16 i;
objlist->seek(OBJLIST_OFFSET_NUM_IN_PARTY);
objlist->write1(num_in_party);
objlist->seek(OBJLIST_OFFSET_PARTY_NAMES);
for (i = 0; i < num_in_party; i++) {
objlist->writeBuf((unsigned char *)member[i].name, PARTY_NAME_MAX_LENGTH + 1);
}
objlist->seek(OBJLIST_OFFSET_PARTY_ROSTER);
for (i = 0; i < num_in_party; i++) {
objlist->write1(member[i].actor->get_actor_num());
}
objlist->seek(OBJLIST_OFFSET_U6_COMBAT_MODE); // combat mode flag. NOTE! this offset is probably U6 specifix FIXME
objlist->write1((uint8)in_combat_mode);
return true;
}
bool Party::add_actor(Actor *actor) {
Converse *converse = game->get_converse();
if (num_in_party < PARTY_MAX_MEMBERS) {
actor->set_in_party(true);
member[num_in_party].actor = actor;
//member[num_in_party].inactive = false;
strncpy(member[num_in_party].name, converse->npc_name(actor->id_n), PARTY_NAME_MAX_LENGTH + 1);
member[num_in_party].name[PARTY_NAME_MAX_LENGTH] = '\0'; // make sure name is terminated
member[num_in_party].combat_position = 0;
// member[num_in_party].leader_x = member[0].actor->get_location().x;
// member[num_in_party].leader_y = member[0].actor->get_location().y;
num_in_party++;
reform_party();
return true;
}
return false;
}
// remove actor from member array shuffle remaining actors down if required.
bool Party::remove_actor(Actor *actor, bool keep_party_flag) {
if (defer_removing_dead_members) //we don't want to remove member while inside the Party::follow() method.
return true;
Game::get_game()->get_event()->set_control_cheat(false);
for (int i = 0; i < num_in_party; i++) {
if (member[i].actor->id_n == actor->id_n) {
if (keep_party_flag == false) {
for (int j = 0; j < member[i].actor->get_num_light_sources(); j++)
subtract_light_source();
member[i].actor->set_in_party(false);
}
if (i != (num_in_party - 1)) {
for (; i + 1 < num_in_party; i++)
member[i] = member[i + 1];
}
num_in_party--;
reform_party();
if (game->is_new_style()) {
Game::get_game()->get_event()->close_gumps();
return true;
}
//FIXME this is a bit hacky we need a better way to refresh views when things change
//maybe using callbacks.
//If the last actor dies and was being displayed in a view then we need to set the
//view to the new last party member.
View *cur_view = Game::get_game()->get_view_manager()->get_current_view();
if (cur_view && cur_view->get_party_member_num() >= num_in_party)
cur_view->set_party_member(num_in_party - 1);
else if (cur_view)
cur_view->set_party_member(cur_view->get_party_member_num()); // needed if a middle party member dies
return true;
}
}
return false;
}
bool Party::remove_dead_actor(Actor *actor) {
return remove_actor(actor, PARTY_KEEP_PARTY_FLAG);
}
bool Party::resurrect_dead_members() {
uint16 i;
Actor *actor;
MapCoord new_pos = get_leader_location();
if (Game::get_game()->get_event()->using_control_cheat()) {
Game::get_game()->get_event()->set_control_cheat(false);
if (!Game::get_game()->is_new_style()) {
Game::get_game()->get_view_manager()->set_inventory_mode();
Game::get_game()->get_view_manager()->get_current_view()->set_party_member(0);
}
}
for (i = 0; i < ACTORMANAGER_MAX_ACTORS; i++) {
actor = actor_manager->get_actor(i);
if (actor->is_in_party() && actor->is_alive() == false)
actor->resurrect(new_pos);
}
update_light_sources(); // should only be needed for control cheat but it won't hurt to recheck
return true;
}
void Party::split_gold() {
}
void Party::gather_gold() {
}
uint8 Party::get_party_size() {
return num_in_party;
}
Actor *Party::get_leader_actor() const {
sint8 leader = get_leader();
if (leader < 0) {
return nullptr;
}
return get_actor(leader);
}
Actor *Party::get_actor(uint8 member_num) const {
if (num_in_party <= member_num)
return nullptr;
return member[member_num].actor;
}
const char *Party::get_actor_name(uint8 member_num) const {
if (num_in_party <= member_num)
return nullptr;
return member[member_num].name;
}
/* Returns position of actor in party or -1.
*/
sint8 Party::get_member_num(const Actor *actor) const {
for (int i = 0; i < num_in_party; i++) {
if (member[i].actor->id_n == actor->id_n)
return i;
}
return -1;
}
sint8 Party::get_member_num(uint8 a) const {
return (get_member_num(actor_manager->get_actor(a)));
}
uint8 Party::get_actor_num(uint8 member_num) const {
if (num_in_party <= member_num)
return 0; // hmm how should we handle this error.
return member[member_num].actor->id_n;
}
/* Rearrange member slot positions based on the number of members and the
* selected formation. Used only when adding or removing actors.
*/
void Party::reform_party() {
uint32 n;
sint32 x, y, max_x;
bool even_row;
sint8 leader = get_leader();
if (leader < 0 || num_in_party == 1)
return;
member[leader].form_x = 0;
member[leader].form_y = 0;
switch (formation) {
case PARTY_FORM_COLUMN: // line up behind Avatar
x = 0;
y = 1;
for (n = leader + 1; n < num_in_party; n++) {
member[n].form_x = x;
member[n].form_y = y++;
if (y == 5) {
x += 1;
y = 0;
}
}
break;
case PARTY_FORM_ROW: // line up left of Avatar
x = -1;
y = 0;
for (n = leader + 1; n < num_in_party; n++) {
member[n].form_x = x--;
member[n].form_y = y;
if (x == -5) {
y += 1;
x = 0;
}
}
break;
case PARTY_FORM_DELTA: // open triangle formation with Avatar at front
x = -1;
y = 1;
for (n = leader + 1; n < num_in_party; n++) {
member[n].form_x = x;
member[n].form_y = y;
// alternate X once, then move down
x = -x;
if (x == 0 || x < 0) {
x -= 1;
++y;
}
if (y == 5) { // start at top of triangle
y -= ((-x) - 1);
x = 0;
}
}
break;
// case PARTY_FORM_COMBAT: // positions determined by COMBAT mode
// break;
case PARTY_FORM_REST: // special formation used while Resting
member[1].form_x = 0;
member[1].form_y = -2;
member[2].form_x = 1;
member[2].form_y = -1;
member[3].form_x = -1;
member[3].form_y = -1;
member[4].form_x = 1;
member[4].form_y = 0;
member[5].form_x = -1;
member[5].form_y = -2;
member[6].form_x = 1;
member[6].form_y = -2;
member[7].form_x = -1;
member[7].form_y = 0;
break;
case PARTY_FORM_STANDARD: // U6
default:
// put first follower behind or behind and to the left of Avatar
member[leader + 1].form_x = (num_in_party >= 3) ? -1 : 0;
member[leader + 1].form_y = 1;
x = y = 1;
even_row = false;
for (n = leader + 2, max_x = 1; n < num_in_party; n++) {
member[n].form_x = x;
member[n].form_y = y;
// alternate & move X left/right by 2
x = (x == 0) ? x - 2 : (x < 0) ? -x : -x - 2;
if (x > max_x || (x < 0 && -x > max_x)) { // reached row max.
++y;
even_row = !even_row; // next row
++max_x; // increase row max.
x = even_row ? 0 : -1; // next guy starts at center or left by 2
}
}
}
}
/* Returns number of person leading the party (the first active member), or -1
* if the party has no leader and can't move. */
sint8 Party::get_leader() const {
for (int m = 0; m < num_in_party; m++)
if (member[m].actor->is_immobile() == false && member[m].actor->is_charmed() == false)
return m;
return -1;
}
/* Get map location of a party member. */
MapCoord Party::get_location(uint8 m) const {
return (member[m].actor->get_location());
}
/* Get map location of the first active party member. */
MapCoord Party::get_leader_location() const {
sint8 m = get_leader();
MapCoord loc;
if (m >= 0)
loc = member[m].actor->get_location();
return loc;
}
/* Returns absolute location where party member `m' SHOULD be (based on party
* formation and leader location.
*/
MapCoord Party::get_formation_coords(uint8 m) const {
MapCoord a = get_location(m); // my location
MapCoord l = get_leader_location(); // leader location
sint8 leader = get_leader();
if (leader < 0)
return a;
uint8 ldir = member[leader].actor->get_direction(); // leader direction
// intended location
uint16 x = (ldir == NUVIE_DIR_N) ? l.x + member[m].form_x : // X
(ldir == NUVIE_DIR_E) ? l.x - member[m].form_y :
(ldir == NUVIE_DIR_S) ? l.x - member[m].form_x :
(ldir == NUVIE_DIR_W) ? l.x + member[m].form_y : a.x;
uint16 y = (ldir == NUVIE_DIR_N) ? l.y + member[m].form_y : // Y
(ldir == NUVIE_DIR_E) ? l.y + member[m].form_x :
(ldir == NUVIE_DIR_S) ? l.y - member[m].form_y :
(ldir == NUVIE_DIR_W) ? l.y - member[m].form_x : a.y;
return (MapCoord(WRAPPED_COORD(x, a.z),
WRAPPED_COORD(y, a.z),
a.z // Z
));
}
/* Update the actual locations of the party actors on the map, so that they are
* in the proper formation. */
void Party::follow(sint8 rel_x, sint8 rel_y) {
Common::Array<bool> try_again;
try_again.resize(get_party_max());
sint8 leader = get_leader();
if (leader <= -1)
return;
if (is_in_combat_mode()) { // just update everyone's combat mode
for (int p = 0; p < get_party_size(); p++)
get_actor(p)->set_worktype(get_actor(p)->get_combat_mode());
return;
}
defer_removing_dead_members = true;
// set previous leader location first, just in case the leader changed
prev_leader_x = WRAPPED_COORD(member[leader].actor->x - rel_x, member[leader].actor->z);
prev_leader_y = member[leader].actor->y - rel_y;
// PASS 1: Keep actors chained together.
for (uint32 p = (leader + 1); p < num_in_party; p++) {
if (member[p].actor->is_immobile()) continue;
try_again[p] = false;
if (!pathfinder->follow_passA(p))
try_again[p] = true;
}
// PASS 2: Catch up to party.
for (uint32 p = (leader + 1); p < num_in_party; p++) {
if (member[p].actor->is_immobile()) continue;
if (try_again[p])
pathfinder->follow_passA(p);
pathfinder->follow_passB(p);
if (!pathfinder->is_contiguous(p)) {
sint8 l = get_leader();
if (l >= 0) {
DEBUG(0, LEVEL_DEBUGGING, "%s is looking for %s.\n", get_actor_name(p), get_actor_name(l));
}
pathfinder->seek_leader(p); // enter/update seek mode
} else if (member[p].actor->get_pathfinder())
pathfinder->end_seek(p);
get_actor(p)->set_moves_left(get_actor(p)->get_moves_left() - 10);
get_actor(p)->set_worktype(0x01); // revert to normal worktype
}
defer_removing_dead_members = false;
//remove party members that died during follow routine.
for (int p = get_party_size() - 1; p >= 0; p--) {
Actor *a = get_actor(p);
if (a->is_alive() == false)
remove_actor(a, PARTY_KEEP_PARTY_FLAG);
}
}
// Returns true if anyone in the party has a matching object.
bool Party::has_obj(uint16 obj_n, uint8 quality, bool match_zero_qual) const {
uint16 i;
for (i = 0; i < num_in_party; i++) {
if (member[i].actor->inventory_get_object(obj_n, quality, match_zero_qual) != nullptr) // we got a match
return true;
}
return false;
}
// Removes the first occurrence of an object in the party.
bool Party::remove_obj(uint16 obj_n, uint8 quality) {
uint16 i;
Obj *obj;
for (i = 0; i < num_in_party; i++) {
obj = member[i].actor->inventory_get_object(obj_n, quality);
if (obj != nullptr) {
if (member[i].actor->inventory_remove_obj(obj)) {
delete_obj(obj);
return true;
}
}
}
return false;
}
// Returns the actor id of the first person in the party to have a matching object.
Actor *Party::who_has_obj(uint16 obj_n, uint8 quality, bool match_qual_zero) {
uint16 i;
for (i = 0; i < num_in_party; i++) {
if (member[i].actor->inventory_get_object(obj_n, quality, match_qual_zero) != nullptr)
return member[i].actor;
}
return nullptr;
}
Obj *Party::get_obj(uint16 obj_n, uint8 quality, bool match_qual_zero, uint8 frame_n, bool match_frame_n) {
Obj *obj;
for (uint16 i = 0; i < num_in_party; i++) {
obj = member[i].actor->inventory_get_object(obj_n, quality, match_qual_zero, frame_n, match_frame_n);
if (obj)
return obj;
}
return nullptr;
}
/* Is EVERYONE in the party at or near the coordinates?
*/
bool Party::is_at(uint16 x, uint16 y, uint8 z, uint32 threshold) const {
for (uint32 p = 0; p < num_in_party; p++) {
MapCoord loc(x, y, z);
if (!member[p].actor->is_nearby(loc, threshold))
return false;
}
return true;
}
bool Party::is_at(const MapCoord &xyz, uint32 threshold) const {
return is_at(xyz.x, xyz.y, xyz.z, threshold);
}
/* Is ANYONE in the party at or near the coordinates? */
bool Party::is_anyone_at(uint16 x, uint16 y, uint8 z, uint32 threshold) const {
for (uint32 p = 0; p < num_in_party; p++) {
MapCoord loc(x, y, z);
if (member[p].actor->is_nearby(loc, threshold))
return true;
}
return false;
}
bool Party::is_anyone_at(const MapCoord &xyz, uint32 threshold) const {
return is_anyone_at(xyz.x, xyz.y, xyz.z, threshold);
}
bool Party::contains_actor(const Actor *actor) const {
if (get_member_num(actor) >= 0)
return true;
return false;
}
bool Party::contains_actor(uint8 actor_num) const {
return (contains_actor(actor_manager->get_actor(actor_num)));
}
void Party::set_in_combat_mode(bool value) {
in_combat_mode = value;
actor_manager->set_combat_movement(value);
if (in_combat_mode) {
for (int p = 0; p < get_party_size(); p++)
get_actor(p)->set_worktype(get_actor(p)->get_combat_mode()); //set combat worktype
} else {
for (int p = 0; p < get_party_size(); p++)
get_actor(p)->set_worktype(ACTOR_WT_FOLLOW); //set back to follow party leader.
}
// if(combat_changes_music)
update_music();
if (game->get_command_bar() != nullptr) {
game->get_command_bar()->set_combat_mode(in_combat_mode);
}
}
void Party::update_music() {
SoundManager *s = Game::get_game()->get_sound_manager();
MapCoord pos;
if (in_vehicle && vehicles_change_music) {
s->musicPlayFrom("boat");
return;
} else if (in_combat_mode && combat_changes_music) {
s->musicPlayFrom("combat");
return;
}
pos = get_leader_location();
switch (pos.z) {
case 0 :
s->musicPlayFrom("random");
break;
case 5 :
s->musicPlayFrom("gargoyle");
break;
default :
s->musicPlayFrom("dungeon");
break;
}
return;
}
void Party::heal() {
uint16 i;
for (i = 0; i < num_in_party; i++) {
member[i].actor->heal();
}
return;
}
void Party::cure() {
for (uint16 i = 0; i < num_in_party; i++) {
member[i].actor->cure();
}
}
void Party::set_ethereal(bool ethereal) {
for (uint16 i = 0; i < num_in_party; i++) {
member[i].actor->set_ethereal(ethereal);
}
}
void Party::show() {
uint16 i;
for (i = 0; i < num_in_party; i++) {
member[i].actor->show();
}
return;
}
void Party::hide() {
uint16 i;
for (i = 0; i < num_in_party; i++) {
member[i].actor->hide();
}
return;
}
/* Move and center everyone in the party to one location.
*/
bool Party::move(uint16 dx, uint16 dy, uint8 dz) {
for (sint32 m = 0; m < num_in_party; m++)
if (!member[m].actor->move(dx, dy, dz, ACTOR_FORCE_MOVE))
return false;
return true;
}
/* Automatically walk (timed) to a destination, and then teleport to new
* location (optional). Used to enter/exit dungeons.
* (step_delay 0 = default speed)
*/
void Party::walk(MapCoord *walkto, MapCoord *teleport, uint32 step_delay) {
if (step_delay)
new TimedPartyMove(walkto, teleport, step_delay);
else
new TimedPartyMove(walkto, teleport);
game->pause_world(); // other actors won't move
game->pause_user(); // don't allow input
// view will snap back to player after everyone has moved
game->get_player()->set_mapwindow_centered(false);
autowalk = true;
}
/* Enter a moongate and teleport to a new location.
* (step_delay 0 = default speed)
*/
void Party::walk(Obj *moongate, MapCoord *teleport, uint32 step_delay) {
MapCoord walkto(moongate->x, moongate->y, moongate->z);
if (step_delay)
new TimedPartyMove(&walkto, teleport, moongate, step_delay);
else
new TimedPartyMove(&walkto, teleport, moongate);
game->pause_world(); // other actors won't move
game->pause_user(); // don't allow input
// view will snap back to player after everyone has moved
game->get_player()->set_mapwindow_centered(false);
autowalk = true;
}
/* Automatically walk (timed) to vehicle. (step_delay 0 = default speed)
*/
void Party::enter_vehicle(Obj *ship_obj, uint32 step_delay) {
MapCoord walkto(ship_obj->x, ship_obj->y, ship_obj->z);
dismount_from_horses();
if (step_delay)
new TimedPartyMoveToVehicle(&walkto, ship_obj, step_delay);
else
new TimedPartyMoveToVehicle(&walkto, ship_obj);
game->pause_world(); // other actors won't move
game->pause_user(); // don't allow input
// view will snap back to player after everyone has moved
game->get_player()->set_mapwindow_centered(false);
autowalk = true;
}
void Party::exit_vehicle(uint16 x, uint16 y, uint16 z) {
if (is_in_vehicle() == false)
return;
Actor *vehicle_actor = actor_manager->get_actor(0);
show();
vehicle_actor->unlink_surrounding_objects();
vehicle_actor->hide();
vehicle_actor->set_worktype(0);
Player *player = game->get_player();
player->set_actor(get_actor(0));
player->move(x, y, z, false);
vehicle_actor->obj_n = 0;//OBJ_U6_NO_VEHICLE;
vehicle_actor->frame_n = 0;
vehicle_actor->init();
vehicle_actor->move(0, 0, 0, ACTOR_FORCE_MOVE);
set_in_vehicle(false);
}
void Party::set_in_vehicle(bool value) {
in_vehicle = value;
if (vehicles_change_music)
update_music();
if (value) {
if (in_combat_mode == true)
set_in_combat_mode(false); // break off combat when boarding a vehicle
}
return;
}
/* Done automatically walking, return view to player character.
*/
void Party::stop_walking(bool force_music_change) {
game->get_player()->set_mapwindow_centered(true);
game->unpause_world(); // allow user input, unfreeze actors
game->unpause_user();
autowalk = false;
if (force_music_change || vehicles_change_music)
update_music();
}
void Party::dismount_from_horses() {
UseCode *usecode = Game::get_game()->get_usecode();
for (uint32 m = 0; m < num_in_party; m++) {
if (member[m].actor->obj_n == OBJ_U6_HORSE_WITH_RIDER) {
Obj *my_obj = member[m].actor->make_obj();
usecode->use_obj(my_obj, member[m].actor);
delete_obj(my_obj);
}
}
return;
}
Actor *Party::get_slowest_actor() {
Actor *actor = nullptr;
sint8 begin = get_leader();
if (begin >= 0) {
actor = member[begin].actor;
sint8 moves = actor->get_moves_left();
for (uint32 m = begin + 1; m < num_in_party; m++) {
sint8 select_moves = member[m].actor->get_moves_left();
if (member[m].actor->is_immobile() == false && (select_moves < moves)) {
moves = select_moves;
actor = member[m].actor;
}
}
}
return actor;
}
/* Gather everyone around a campfire to Rest. */
void Party::rest_gather() {
Actor *player_actor = get_leader_actor();
if (player_actor) {
MapCoord player_loc = player_actor->get_location();
rest_campfire = new_obj(OBJ_U6_CAMPFIRE, 1, player_loc.x, player_loc.y, player_loc.z);
rest_campfire->set_temporary();
rest_campfire->qty = 1; //this is set so the campfire may be destroyed by being attacked.
game->get_obj_manager()->add_obj(rest_campfire, true); // addOnTop
game->get_player()->set_mapwindow_centered(false);
game->pause_user();
new TimedRestGather(player_loc.x, player_loc.y);
}
}
/* Start Resting for the specified number of hours, optionally with a party
* member standing guard. */
void Party::rest_sleep(uint8 hours, sint16 guard) {
// FIXME: change music to Stones when asking "How many hours?", change to
// a random song when finished camping (or if cancelled)
new TimedRest(hours, guard >= 0 ? member[guard].actor : 0, rest_campfire);
}
bool Party::can_rest(Std::string &err_str) {
Map *map_ = game->get_game_map();
Player *player = game->get_player();
Actor *pActor = player->get_actor();
MapCoord loc = pActor->get_location();
ActorList *enemies = nullptr;
ActorList *all_actors = nullptr;
if (is_in_combat_mode()) {
if (Game::get_game()->get_game_type() == NUVIE_GAME_SE)
err_str = "\nNot while in Combat mode!";
else if (Game::get_game()->get_game_type() == NUVIE_GAME_MD)
err_str = "- Not while in Combat!";
else
err_str = "-Not while in Combat!";
} else if (is_in_vehicle()
&& pActor->get_obj_n() != OBJ_U6_SHIP) // player is a vehicle
err_str = "-Can not be repaired!";
else if (Game::get_game()->get_game_type() == NUVIE_GAME_U6
&& game->get_map_window()->in_town())
err_str = "-Only in the wilderness!";
else if ((enemies = pActor->find_enemies())) {
if (Game::get_game()->get_game_type() == NUVIE_GAME_MD)
err_str = "\nNot while foes are near!";
if (Game::get_game()->get_game_type() == NUVIE_GAME_SE)
err_str = "- Not while foes are near!";
else
err_str = "-Not while foes are near!";
} else if ((all_actors = actor_manager->filter_party(actor_manager->filter_distance(actor_manager->get_actor_list(),
loc.x, loc.y, loc.z, 5)))
&& !all_actors->empty() && !is_in_vehicle()) {
if (Game::get_game()->get_game_type() == NUVIE_GAME_U6)
err_str = "-Not while others are near!";
else
err_str = "\nIt's too noisy to sleep here!";
delete all_actors;
} else if (!player->in_party_mode())
err_str = "-Not in solo mode!";
else if (!is_in_vehicle() && !map_->is_passable(loc.x - 1, loc.y - 1, loc.x + 1, loc.y + 1, loc.z)
&& Game::get_game()->get_game_type() != NUVIE_GAME_SE)
err_str = "-Not enough room!"; // FIXME: for ships the original checks all squares around the ship. Do we really need this?
else if (is_horsed())
err_str = "-Dismount first!";
else
return true;
delete enemies;
return false;
}
bool Party::is_horsed() const {
for (int p = 0; p < num_in_party; p++)
if (member[p].actor->get_obj_n() == OBJ_U6_HORSE_WITH_RIDER)
return true;
return false;
}
bool Party::is_everyone_horsed() const {
for (int p = 0; p < num_in_party; p++)
if (member[p].actor->get_obj_n() != OBJ_U6_HORSE_WITH_RIDER)
return false;
return true;
}
Obj *Party::get_food() {
for (int p = 0; p < num_in_party; p++) {
Obj *food = member[p].actor->inventory_get_food();
if (food)
return food;
}
return 0;
}
void Party::set_combat_target(uint8 member_num, Actor *target) {
if (num_in_party <= member_num)
return;
member[member_num].target.type = TARGET_ACTOR;
member[member_num].target.actor_num = target->get_actor_num();
}
void Party::set_combat_target(uint8 member_num, MapCoord target) {
if (num_in_party <= member_num)
return;
member[member_num].target.type = TARGET_LOCATION;
member[member_num].target.loc = target;
}
void Party::clear_combat_target(uint8 member_num) {
if (member_num >= PARTY_MAX_MEMBERS)
return;
member[member_num].target.type = TARGET_NONE;
member[member_num].target.loc = MapCoord();
member[member_num].target.actor_num = 0;
}
CombatTarget Party::get_combat_target(uint8 member_num) {
if (num_in_party <= member_num) {
CombatTarget noTarget;
noTarget.type = TARGET_NONE;
noTarget.loc = MapCoord();
noTarget.actor_num = 0;
return noTarget;
}
return member[member_num].target;
}
void Party::update_light_sources() {
lightsources = 0;
for (int i = 0; i < num_in_party; i++) {
for (int j = 0; j < member[i].actor->get_num_light_sources(); j++)
add_light_source();
}
if (game->get_event()->using_control_cheat()) {
for (int i = 0; i < game->get_player()->get_actor()->get_num_light_sources(); i++)
add_light_source();
}
game->get_map_window()->updateAmbience();
}
bool Party::has_light_source() {
if (!game->get_player()->get_actor())
return false;
if (lightsources > 0) { // the original engine didn't care about distance
if (game->get_event()->using_control_cheat()) {
if (game->get_player()->get_actor()->get_num_light_sources() > 0)
return true;
else
return false;
}
for (int i = 0; i < num_in_party; i++) {
if (member[i].actor->get_num_light_sources() > 0) {
if (!game->get_map_window()->tile_is_black(member[i].actor->x, member[i].actor->y)
&& member[i].actor->is_nearby(game->get_player()->get_actor())) // within 5 tiles of player
return true;
}
}
}
return false;
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,233 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_PARTY_H
#define NUVIE_CORE_PARTY_H
#include "ultima/shared/std/string.h"
#include "ultima/nuvie/core/obj_manager.h"
#include "ultima/nuvie/core/map.h"
namespace Ultima {
namespace Nuvie {
class Configuration;
class Game;
class Actor;
class ActorManager;
class MapCoord;
class Map;
class NuvieIO;
class PartyPathFinder;
class PartySeek;
typedef enum { TARGET_ACTOR, TARGET_LOCATION, TARGET_NONE } CombatTargetType;
struct CombatTarget {
CombatTargetType type;
uint8 actor_num;
MapCoord loc;
};
struct PartyMember {
char name[14];
Actor *actor;
//bool inactive; // true if not in formation
uint8 combat_position;
sint8 form_x; // relative position left or right of leader
sint8 form_y; // relative position in front or in back of leader
// (leader is at 0,0 in formation)
CombatTarget target;
};
#define PARTY_MAX_MEMBERS 16
#define PARTY_NAME_MAX_LENGTH 13
#define PARTY_KEEP_PARTY_FLAG true
/* party walking formations: */
#define PARTY_FORM_STANDARD 0
#define PARTY_FORM_COLUMN 1
#define PARTY_FORM_ROW 2
#define PARTY_FORM_DELTA 3
#define PARTY_FORM_COMBAT 7
#define PARTY_FORM_REST 8
/* 0 <- standard *
* 1 2 *
* 4 3 5 *
* 6 7 *
* *
* 0 <- column * 3210 <- row
* 1 * 7654
* 2 *
* 3... *
* *
* 0 <- delta
* 172
* 38 94
* 5A B6
*
* (Combat positions are dynamic, based on combat mode)
*/
class Party {
protected:
friend class PartyPathFinder;
Game *game; // get pointers here to avoid construct order issues in loadGame()
const Configuration *config;
ActorManager *actor_manager;
Map *map;
PartyPathFinder *pathfinder;
PartyMember member[PARTY_MAX_MEMBERS];
uint8 lightsources;
uint8 num_in_party; // number of party members.
uint8 formation; // walking formation
uint16 prev_leader_x; // last location of leader
uint16 prev_leader_y;
bool autowalk; // party is automatically walking to a destination
bool in_vehicle; //Party is in a vehicle.
bool in_combat_mode;
bool defer_removing_dead_members;
Obj *rest_campfire;
public:
Party(const Configuration *cfg);
virtual ~Party();
virtual bool init(Game *g, ActorManager *am);
virtual bool load(NuvieIO *objlist);
virtual bool save(NuvieIO *objlist);
// Basic methods
void follow(sint8 rel_x, sint8 rel_y); // follow in direction leader moved
bool move(uint16 dx, uint16 dy, uint8 dz);
void show(); // Actor::show()
void hide(); // Actor::hide()
virtual void dismount_from_horses();
virtual void update_music(); // set music depending on party location
virtual void split_gold();
virtual void gather_gold();
bool add_actor(Actor *actor);
bool remove_actor(Actor *actor, bool keep_party_flag = false);
bool remove_dead_actor(Actor *actor);
bool resurrect_dead_members();
void heal();
void cure();
void set_ethereal(bool ethereal);
//void set_active(uint8 member_num, bool state) { member[member_num].inactive = !state; }
uint8 get_formation() const {
return formation; // walking formation
}
void set_formation(uint8 val) {
formation = val;
reform_party();
}
// Properties
uint8 get_party_size();
virtual uint8 get_party_max() {
return 8; // U6
}
sint8 get_leader() const; // returns -1 if party has no leader and can't move
MapCoord get_leader_location() const;
MapCoord get_location(uint8 m = 0) const;
MapCoord get_formation_coords(uint8 m) const;
void set_in_vehicle(bool value);
void set_in_combat_mode(bool value);
bool is_in_vehicle() const {
return in_vehicle;
}
bool is_in_combat_mode() const {
return in_combat_mode;
}
Actor *get_slowest_actor(); // actor with lowest move count
// Check specific actors
uint8 get_actor_num(uint8 member_num) const; //get actor id_n from party_member num.
Actor *get_actor(uint8 member_num) const;
sint8 get_member_num(const Actor *actor) const;
sint8 get_member_num(uint8 a) const;
Actor *get_leader_actor() const;
const char *get_actor_name(uint8 member_num) const;
bool is_leader(const Actor *actor) const {
return (get_member_num(actor) == get_leader());
}
bool contains_actor(const Actor *actor) const;
bool contains_actor(uint8 actor_num) const;
// Check entire party
bool is_at(uint16 x, uint16 y, uint8 z, uint32 threshold = 0) const;
bool is_at(const MapCoord &xyz, uint32 threshold = 0) const;
bool is_anyone_at(uint16 x, uint16 y, uint8 z, uint32 threshold = 0) const;
bool is_anyone_at(const MapCoord &xyz, uint32 threshold = 0) const;
bool has_obj(uint16 obj_n, uint8 quality, bool match_zero_qual = true) const;
bool remove_obj(uint16 obj_n, uint8 quality);
Actor *who_has_obj(uint16 obj_n, uint8 quality, bool match_zero_qual = true);
Obj *get_obj(uint16 obj_n, uint8 quality, bool match_qual_zero = true, uint8 frame_n = 0, bool match_frame_n = false);
bool is_horsed() const; // is anyone on a horse?
bool is_everyone_horsed() const;
Obj *get_food(); // used while resting
// Automatic-walking. These methods should be replaced with ActorActions.
void walk(MapCoord *walkto, MapCoord *teleport, uint32 step_delay = 0);
void walk(MapCoord *walkto, uint32 step_delay = 0) {
walk(walkto, nullptr, step_delay);
}
void walk(Obj *moongate, MapCoord *teleport, uint32 step_delay = 0);
void enter_vehicle(Obj *ship_obj, uint32 step_delay = 0);
void exit_vehicle(uint16 x, uint16 y, uint16 z);
void stop_walking(bool force_music_change);
bool get_autowalk() const {
return autowalk;
}
void rest_gather();
void rest_sleep(uint8 hours, sint16 guard);
bool can_rest(Std::string &err_str);
void set_combat_target(uint8 member_num, Actor *target);
void set_combat_target(uint8 member_num, MapCoord target);
void clear_combat_target(uint8 member_num);
CombatTarget get_combat_target(uint8 member_num);
bool has_light_source();
void add_light_source() {
lightsources++; /* fprintf(stderr, "lightsources = %d\n", lightsources); */
}
void subtract_light_source() { /*assert(lightsources != 0);*/
lightsources--; /*fprintf(stderr, "lightsources = %d\n", lightsources); */
}
void update_light_sources();
bool combat_changes_music, vehicles_change_music;
protected:
void reform_party(); // call when adding or removing members
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,743 @@
/* 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/conf/configuration.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/files/nuvie_io.h"
#include "ultima/nuvie/actors/actor_manager.h"
#include "ultima/nuvie/actors/actor.h"
#include "ultima/nuvie/core/obj_manager.h"
#include "ultima/nuvie/gui/widgets/map_window.h"
#include "ultima/nuvie/core/game_clock.h"
#include "ultima/nuvie/gui/widgets/msg_scroll.h"
#include "ultima/nuvie/core/party.h"
#include "ultima/nuvie/save/obj_list.h"
#include "ultima/nuvie/core/u6_objects.h"
#include "ultima/nuvie/sound/sound_manager.h"
#include "ultima/nuvie/core/weather.h"
#include "ultima/nuvie/script/script.h"
#include "ultima/nuvie/core/player.h"
namespace Ultima {
namespace Nuvie {
static const int PLAYER_BASE_MOVEMENT_COST = 5;
Player::Player(const Configuration *cfg) : config(cfg), _clock(nullptr),
party(nullptr), actor(nullptr), actor_manager(nullptr), obj_manager(nullptr),
map_window(nullptr), karma(0), gender(0), questf(0), gargishf(0), alcohol(0),
current_weapon(0), party_mode(false), mapwindow_centered(false) {
config->value("config/GameType", game_type);
}
bool Player::init(ObjManager *om, ActorManager *am, MapWindow *mw, GameClock *c, Party *p) {
_clock = c;
actor_manager = am;
obj_manager = om;
map_window = mw;
party = p;
current_weapon = -1;
init();
return true;
}
void Player::init() {
actor = nullptr;
party_mode = true;
mapwindow_centered = true;
}
bool Player::load(NuvieIO *objlist) {
uint8 solo_member_num = OBJLIST_PARTY_MODE;
init();
// We can get the name from the player actor. --SB-X
/* objlist->seek(0xf00);
objlist->readToBuf((unsigned char *)name,14); // read in Player name.*/
if (game_type == NUVIE_GAME_U6) {
objlist->seek(OBJLIST_OFFSET_U6_QUEST_FLAG); // U6 Quest Flag
questf = objlist->read1();
objlist->seek(OBJLIST_OFFSET_U6_KARMA); // Player Karma.
karma = objlist->read1();
objlist->seek(OBJLIST_OFFSET_U6_ALCOHOL); // Alcohol consumed
alcohol = objlist->read1();
objlist->seek(OBJLIST_OFFSET_U6_GARGISH_LANG); // U6 Gargish Flag
gargishf = objlist->read1();
objlist->seek(OBJLIST_OFFSET_U6_SOLO_MODE); //Party Mode = 0xff other wise it is solo mode party member number starting from 0.
solo_member_num = objlist->read1();
objlist->seek(OBJLIST_OFFSET_U6_GENDER); // Player Gender.
gender = objlist->read1();
}
if (game_type == NUVIE_GAME_MD) {
objlist->seek(OBJLIST_OFFSET_MD_GENDER); // Player Gender.
gender = objlist->read1();
}
if (solo_member_num == OBJLIST_PARTY_MODE) {
party_mode = true;
set_party_mode(find_actor());
} else
set_solo_mode(party->get_actor(solo_member_num));
return true;
}
bool Player::save(NuvieIO *objlist) {
if (game_type == NUVIE_GAME_U6) {
objlist->seek(OBJLIST_OFFSET_U6_QUEST_FLAG); // U6 Quest Flag
objlist->write1(questf);
objlist->seek(OBJLIST_OFFSET_U6_KARMA); // Player Karma.
objlist->write1(karma);
objlist->seek(OBJLIST_OFFSET_U6_ALCOHOL); // Alcohol consumed
objlist->write1(alcohol);
objlist->seek(OBJLIST_OFFSET_U6_GARGISH_LANG); // U6 Gargish Flag
objlist->write1(gargishf);
objlist->seek(OBJLIST_OFFSET_U6_SOLO_MODE); // solo member num.
if (party_mode)
objlist->write1(OBJLIST_PARTY_MODE); // 0xff
else
objlist->write1(party->get_member_num(actor)); //write the party member number of the solo actor
objlist->seek(OBJLIST_OFFSET_U6_GENDER); // Player Gender.
objlist->write1(gender);
}
if (game_type == NUVIE_GAME_MD) {
objlist->seek(OBJLIST_OFFSET_MD_GENDER); // Player Gender.
objlist->write1(gender);
}
return true;
}
Actor *Player::find_actor() {
for (int p = 0; p < ACTORMANAGER_MAX_ACTORS; p++) {
Actor *theActor = actor_manager->get_actor(p);
if (theActor->get_worktype() == 0x02 && theActor->is_immobile() == false) // WT_U6_PLAYER
return theActor;
}
sint8 party_leader = party->get_leader();
if (party_leader != -1)
return party->get_actor(party_leader);
return actor_manager->get_avatar();
}
// keep MapWindow focused on Player actor, or remove focus
void Player::set_mapwindow_centered(bool state) {
uint16 x, y;
uint8 z;
mapwindow_centered = state;
if (mapwindow_centered == false)
return;
map_window->centerMapOnActor(actor); // center immediately
get_location(&x, &y, &z);
actor_manager->updateActors(x, y, z);
obj_manager->update(x, y, z); // spawn eggs when teleporting. eg red moongate.
}
void Player::set_actor(Actor *new_actor) {
MsgScroll *scroll = Game::get_game()->get_scroll();
if (new_actor == nullptr) {
return;
}
if (actor != nullptr) {
if (party->contains_actor(actor))
actor->set_worktype(0x01); //WT_U6_IN_PARTY
else
actor->set_worktype(0x00); //no worktype
}
bool same_actor = (actor == new_actor);
actor = new_actor;
actor->set_worktype(0x02); // WT_U6_PLAYER
actor->delete_pathfinder();
current_weapon = ACTOR_NO_READIABLE_LOCATION;
map_window->centerCursor();
if (same_actor)
return;
actor_manager->set_player(actor);
Std::string prompt = get_name();
if (game_type == NUVIE_GAME_U6) {
prompt += ":\n";
}
prompt += ">";
scroll->set_prompt(prompt.c_str());
}
Actor *Player::get_actor() {
return actor;
}
const Actor *Player::get_actor() const {
return actor;
}
void Player::get_location(uint16 *ret_x, uint16 *ret_y, uint8 *ret_level) const {
actor->get_location(ret_x, ret_y, ret_level);
}
uint8 Player::get_location_level() const {
return actor->z;
}
const char *Player::get_name() {
if (actor->get_actor_num() == ACTOR_VEHICLE_ID_N)
return actor_manager->get_avatar()->get_name();
return actor->get_name(true);
}
/* Add to Player karma. Handle appropriately the karma min/max limits. */
void Player::add_karma(uint8 val) {
karma = ((karma + val) <= 99) ? karma + val : 99;
}
/* Subtract from Player karma. Handle appropriately the karma min/max limits. */
void Player::subtract_karma(uint8 val) {
karma = ((karma - val) >= 0) ? karma - val : 0;
}
void Player::subtract_movement_points(uint8 points) {
Game::get_game()->get_script()->call_actor_subtract_movement_points(get_actor(), points);
}
const char *Player::get_gender_title() const {
switch (game_type) {
case NUVIE_GAME_U6 :
if (gender == 0)
return "milord";
else
return "milady";
case NUVIE_GAME_MD :
if (gender == 0)
return "Sir";
else
return "Madam";
default :
break;
}
return "Sir"; //FIXME is this needed for SE?
}
bool Player::check_moveRelative(sint16 rel_x, sint16 rel_y) {
if (!actor->moveRelative(rel_x, rel_y, ACTOR_IGNORE_DANGER)) { /**MOVE**/
ActorError *ret = actor->get_error();
// FIXME: When in combat, U6 attempts to move party members with role "front" forward instead of swapping.
if (ret->err == ACTOR_BLOCKED_BY_ACTOR
&& (game_type != NUVIE_GAME_U6 || actor->obj_n != OBJ_U6_MOUSE) // Only exchange positions if player is not U6 mouse
&& party->contains_actor(ret->blocking_actor) && ret->blocking_actor->is_immobile() == false)
ret->blocking_actor->push(actor, ACTOR_PUSH_HERE);
// There could be more party members at the destination, but U6 ignores them - hence the ACTOR_IGNORE_PARTY_MEMBERS.
if (!actor->moveRelative(rel_x, rel_y, ACTOR_IGNORE_DANGER | ACTOR_IGNORE_PARTY_MEMBERS)) /**MOVE**/
return false;
}
return true;
}
// walk to adjacent square
void Player::moveRelative(sint16 rel_x, sint16 rel_y, bool mouse_movement) {
const NuvieDir raft_movement_tbl[] = {
NUVIE_DIR_N, NUVIE_DIR_NE, NUVIE_DIR_N, NUVIE_DIR_NW, NUVIE_DIR_N, NUVIE_DIR_NE, NUVIE_DIR_NW, NUVIE_DIR_N,
NUVIE_DIR_NE, NUVIE_DIR_NE, NUVIE_DIR_E, NUVIE_DIR_N, NUVIE_DIR_NE, NUVIE_DIR_E, NUVIE_DIR_NE, NUVIE_DIR_N,
NUVIE_DIR_NE, NUVIE_DIR_E, NUVIE_DIR_SE, NUVIE_DIR_E, NUVIE_DIR_E, NUVIE_DIR_E, NUVIE_DIR_SE, NUVIE_DIR_NE,
NUVIE_DIR_E, NUVIE_DIR_SE, NUVIE_DIR_SE, NUVIE_DIR_S, NUVIE_DIR_E, NUVIE_DIR_SE, NUVIE_DIR_S, NUVIE_DIR_SE,
NUVIE_DIR_S, NUVIE_DIR_SE, NUVIE_DIR_S, NUVIE_DIR_SW, NUVIE_DIR_SE, NUVIE_DIR_S, NUVIE_DIR_S, NUVIE_DIR_SW,
NUVIE_DIR_W, NUVIE_DIR_S, NUVIE_DIR_SW, NUVIE_DIR_SW, NUVIE_DIR_SW, NUVIE_DIR_S, NUVIE_DIR_SW, NUVIE_DIR_W,
NUVIE_DIR_NW, NUVIE_DIR_W, NUVIE_DIR_SW, NUVIE_DIR_W, NUVIE_DIR_NW, NUVIE_DIR_SW, NUVIE_DIR_W, NUVIE_DIR_W,
NUVIE_DIR_NW, NUVIE_DIR_N, NUVIE_DIR_W, NUVIE_DIR_NW, NUVIE_DIR_N, NUVIE_DIR_NW, NUVIE_DIR_W, NUVIE_DIR_NW
};
const uint8 ship_cost[8] = {0xA, 5, 3, 4, 5, 4, 3, 5};
const uint8 skiff_cost[8] = {3, 4, 5, 7, 0xA, 7, 5, 4};
MovementStatus movementStatus = CAN_MOVE;
bool can_change_rel_dir = true;
NuvieDir wind_dir = Game::get_game()->get_weather()->get_wind_dir();
uint16 x, y;
uint8 z;
actor->get_location(&x, &y, &z);
if (game_type == NUVIE_GAME_U6) {
if (actor->id_n == 0) { // vehicle actor
if (actor->obj_n == OBJ_U6_INFLATED_BALLOON &&
(!Game::get_game()->has_free_balloon_movement() || !party->has_obj(OBJ_U6_FAN, 0, false))) {
can_change_rel_dir = false;
NuvieDir dir = get_reverse_direction(Game::get_game()->get_weather()->get_wind_dir());
if (dir == NUVIE_DIR_NONE) {
Game::get_game()->get_scroll()->display_string("Thou canst not move without wind!\n\n");
Game::get_game()->get_scroll()->display_prompt();
actor->set_moves_left(0);
rel_x = 0;
rel_y = 0;
} else {
get_relative_dir(dir, &rel_x, &rel_y);
}
} else if (actor->obj_n == OBJ_U6_RAFT) {
NuvieDir dir = NUVIE_DIR_N;
can_change_rel_dir = false;
const Tile *t = Game::get_game()->get_game_map()->get_tile(x, y, z, true);
if (t->flags1 & TILEFLAG_BLOCKING) { //deep water tiles are blocking. Shore tiles should allow player movement.
//deep water, so take control away from player.
if (t->tile_num >= 8 && t->tile_num < 16) {
dir = static_cast<NuvieDir>(t->tile_num - 8);
}
if (wind_dir != NUVIE_DIR_NONE) {
dir = raft_movement_tbl[dir * 8 + get_reverse_direction(wind_dir)];
} else {
dir = get_nuvie_dir_code(dir);
}
get_relative_dir(dir, &rel_x, &rel_y);
}
}
} else { // normal actor
if (alcohol > 3 && NUVIE_RAND() % 4 != 0) {
rel_x = NUVIE_RAND() % 3 - 1; // stumble and change direction
rel_y = NUVIE_RAND() % 3 - 1;
can_change_rel_dir = false;
Game::get_game()->get_scroll()->display_string("Hic!\n");
}
}
ActorMoveFlags move_flags = ACTOR_IGNORE_DANGER | ACTOR_IGNORE_PARTY_MEMBERS;
// don't allow diagonal move between blocked tiles (player only)
if (rel_x && rel_y && !actor->check_move(x + rel_x, y + 0, z, move_flags)
&& !actor->check_move(x + 0, y + rel_y, z, move_flags)) {
movementStatus = BLOCKED;
}
} else if (game_type == NUVIE_GAME_MD) {
if (Game::get_game()->get_clock()->get_timer(GAMECLOCK_TIMER_MD_BLUE_BERRY) != 0 && NUVIE_RAND() % 2 == 0) {
rel_x = NUVIE_RAND() % 3 - 1; // stumble and change direction
rel_y = NUVIE_RAND() % 3 - 1;
can_change_rel_dir = false;
Game::get_game()->get_scroll()->display_string("you are dizzy!\n"); //FIXME need i18n support here.
}
}
if (actor->is_immobile() && actor->id_n != 0)
movementStatus = BLOCKED;
if (movementStatus != BLOCKED && game_type != NUVIE_GAME_U6) {
movementStatus = Game::get_game()->get_script()->call_player_before_move_action(&rel_x, &rel_y);
}
if (movementStatus != BLOCKED) {
if (movementStatus == FORCE_MOVE) {
actor->moveRelative(rel_x, rel_y, ACTOR_FORCE_MOVE);
} else if (!check_moveRelative(rel_x, rel_y)) {
movementStatus = BLOCKED;
if (mouse_movement && rel_x != 0 && rel_y != 0 && can_change_rel_dir) {
if (check_moveRelative(rel_x, 0)) { // try x movement only
rel_y = 0;
movementStatus = CAN_MOVE;
} else if (check_moveRelative(0, rel_y)) { // try y movement only
rel_x = 0;
movementStatus = CAN_MOVE;
}
}
// Try opening a door FIXME: shouldn't be U6 specific
if (movementStatus == BLOCKED) {
if (obj_manager->is_door(x + rel_x, y + rel_y, z))
try_open_door(x + rel_x, y + rel_y, z);
}
if (movementStatus == BLOCKED) {
Game::get_game()->get_sound_manager()->playSfx(NUVIE_SFX_BLOCKED);
if (actor->id_n == 0) //vehicle actor.
actor->set_moves_left(0); //zero movement points here so U6 can change wind direction by advancing game time.
}
}
}
actor->set_direction(rel_x, rel_y);
// post-move
if (movementStatus != BLOCKED) {
if (party_mode && party->is_leader(actor)) { // lead party
party->follow(rel_x, rel_y);
} else if (actor->id_n == 0 && game_type != NUVIE_GAME_MD) { // using vehicle; drag party along
MapCoord new_xyz = actor->get_location();
party->move(new_xyz.x, new_xyz.y, new_xyz.z);
}
if (game_type == NUVIE_GAME_U6 && (actor->obj_n == OBJ_U6_INFLATED_BALLOON || actor->obj_n == OBJ_U6_RAFT)) {
actor->set_moves_left(actor->get_moves_left() - PLAYER_BASE_MOVEMENT_COST);
} else if (game_type == NUVIE_GAME_U6 && actor->obj_n == OBJ_U6_SHIP && wind_dir != WEATHER_WIND_CALM) {
NuvieDir nuvie_dir = get_direction_code(rel_x, rel_y);
if (nuvie_dir != NUVIE_DIR_NONE) {
sint8 dir = get_original_dir_code(nuvie_dir);
actor->set_moves_left(actor->get_moves_left() - ship_cost[abs(dir - wind_dir)]);
//DEBUG(0, LEVEL_DEBUGGING, "Ship movement cost = %d\n", ship_cost[abs(dir-wind_dir)]);
}
} else if (game_type == NUVIE_GAME_U6 && actor->obj_n == OBJ_U6_SKIFF) {
NuvieDir nuvie_dir = get_direction_code(rel_x, rel_y);
if (nuvie_dir != NUVIE_DIR_NONE) {
sint8 dir = get_original_dir_code(nuvie_dir);
sint8 water_dir = dir;
const Tile *t = Game::get_game()->get_game_map()->get_tile(x, y, z, true);
if (t->tile_num >= 8 && t->tile_num < 16) {
dir = t->tile_num - 8;
}
actor->set_moves_left(actor->get_moves_left() - skiff_cost[abs(dir - water_dir)]);
//DEBUG(0, LEVEL_DEBUGGING, "Skiff movement cost = %d\n", skiff_cost[abs(dir-water_dir)]);
}
} else {
actor->set_moves_left(actor->get_moves_left() - (PLAYER_BASE_MOVEMENT_COST + Game::get_game()->get_game_map()->get_impedance(x, y, z)));
if (rel_x != 0 && rel_y != 0) // diagonal move, double cost
actor->set_moves_left(actor->get_moves_left() - PLAYER_BASE_MOVEMENT_COST);
}
}
if (game_type != NUVIE_GAME_U6) {
Game::get_game()->get_script()->call_player_post_move_action(movementStatus != BLOCKED);
actor->get_location(&x, &y, &z); //update location in case we have moved.
}
// update world around player
actor_manager->updateActors(x, y, z);
obj_manager->update(x, y, z); // remove temporary objs, hatch eggs
_clock->inc_move_counter(); // doesn't update time
actor_manager->startActors(); // end player turn
}
void Player::try_open_door(uint16 x, uint16 y, uint8 z) {
UseCode *usecode = Game::get_game()->get_usecode();
Obj *obj = obj_manager->get_obj(x, y, z);
if (!usecode->is_door(obj))
return;
usecode->use_obj(obj, get_actor());
subtract_movement_points(MOVE_COST_USE);
map_window->updateBlacking();
}
// teleport-type move
void Player::move(sint16 new_x, sint16 new_y, uint8 new_level, bool teleport) {
if (actor->move(new_x, new_y, new_level, ACTOR_FORCE_MOVE)) {
//map_window->centerMapOnActor(actor);
if (party->is_leader(actor)) { // lead party
if (actor_manager->get_avatar()->get_hp() == 0) { // need to end turn if Avatar died
actor_manager->startActors();
return;
}
party->move(new_x, new_y, new_level); // center everyone first
party->follow(0, 0); // then try to move them to correct positions
}
actor_manager->updateActors(new_x, new_y, new_level);
if (teleport && new_level != 0 && new_level != 5)
Game::get_game()->get_weather()->set_wind_dir(NUVIE_DIR_NONE);
obj_manager->update(new_x, new_y, new_level, teleport); // remove temporary objs, hatch eggs
// it's still the player's turn
}
}
void Player::moveLeft() {
moveRelative(-1, 0);
}
void Player::moveRight() {
moveRelative(1, 0);
}
void Player::moveUp() {
moveRelative(0, -1);
}
void Player::moveDown() {
moveRelative(0, 1);
}
void Player::pass() {
Game::get_game()->get_script()->call_player_pass();
// uint16 x = actor->x, y = actor->y;
// uint8 z = actor->z;
// Move balloon / raft if required.
if (game_type == NUVIE_GAME_U6 && (actor->obj_n == OBJ_U6_INFLATED_BALLOON || actor->obj_n == OBJ_U6_RAFT)) {
if (Game::get_game()->get_weather()->get_wind_dir() != NUVIE_DIR_NONE) {
moveRelative(0, 0);
//return;
}
}
if (actor->get_moves_left() > 0)
actor->set_moves_left(0); // Pass and use up moves
// update world around player
if (party_mode && party->is_leader(actor)) // lead party
party->follow(0, 0);
// actor_manager->updateActors(x, y, z); // not needed because position is unchanged
_clock->inc_move_counter_by_a_minute(); // doesn't update time
actor_manager->startActors(); // end player turn
//actor_manager->moveActors();
Game::get_game()->time_changed();
}
/* Enter party mode, with everyone following actor. (must be in the party)
*/
bool Player::set_party_mode(Actor *new_actor) {
if (party->contains_actor(new_actor) || party->is_in_vehicle()) {
party_mode = true;
set_actor(new_actor);
return true;
}
return false;
}
/* Enter solo mode as actor. (must be in the party)
*/
bool Player::set_solo_mode(Actor *new_actor) {
if (party->contains_actor(new_actor)) {
if (new_actor->is_charmed()) {
Game::get_game()->get_scroll()->display_fmt_string("%s fails to respond.\n\n", new_actor->get_name());
return false;
}
party_mode = false;
set_actor(new_actor);
return true;
}
return false;
}
/* Returns the delay in continuous movement for the actor type we control.
*/
uint32 Player::get_walk_delay() const {
if (game_type != NUVIE_GAME_U6)
return 125; // normal movement about 8 spaces per second
if (actor->obj_n == OBJ_U6_BALLOON_BASKET)
return 10; // 10x normal (wow!)
else if (actor->obj_n == OBJ_U6_SHIP)
return 20; // 5x normal
else if (actor->obj_n == OBJ_U6_SKIFF)
return 50; // 2x normal
else if (actor->obj_n == OBJ_U6_RAFT)
return 100; // normal
else if (actor->obj_n == OBJ_U6_HORSE_WITH_RIDER && party->is_everyone_horsed())
return 50; // 2x normal
else
return 125; // normal movement about 8 spaces per second
}
/* Returns true if it's time for the player to take another step.
* (call during continuous movement)
*/
bool Player::check_walk_delay() {
static uint32 walk_delay = 0, // start with no delay
last_time = _clock->get_ticks();
uint32 this_time = _clock->get_ticks();
uint32 time_passed = this_time - last_time;
// subtract time_passed until delay is 0
if (sint32(walk_delay - time_passed) < 0)
walk_delay = 0;
else
walk_delay -= time_passed;
last_time = this_time; // set each call to get time_passed
if (walk_delay == 0) {
walk_delay = get_walk_delay(); // reset
return true;
}
return false; // not time yet
}
bool Player::weapon_can_hit(uint16 x, uint16 y) {
return actor->weapon_can_hit(actor->get_weapon(current_weapon), x, y);
}
void Player::attack_select_init(bool use_attack_text) {
uint16 x, y;
uint8 z;
current_weapon = ACTOR_NO_READIABLE_LOCATION;
if (attack_select_next_weapon(false, use_attack_text) == false)
attack_select_weapon_at_location(ACTOR_NO_READIABLE_LOCATION, use_attack_text); // attack with hands.
map_window->centerCursor();
CombatTarget target = party->get_combat_target(actor->id_n == 0 ? 0 : party->get_member_num(actor));
Actor *target_actor = nullptr;
switch (target.type) {
case TARGET_ACTOR :
target_actor = actor_manager->get_actor(target.actor_num);
uint16 target_x, target_y;
map_window->get_pos(&x, &y, &z);
target_x = x;
target_y = y;
if (target_actor && target_actor->is_onscreen() && target_actor->is_alive() && target_actor->is_visible() && actor->weapon_can_hit(actor->get_weapon(current_weapon), target_actor, &target_x, &target_y)) {
map_window->moveCursor(target_x - x, target_y - y);
} else {
party->clear_combat_target(actor->id_n == 0 ? 0 : party->get_member_num(actor));
}
break;
case TARGET_LOCATION :
if (target.loc.z == actor->get_z() && weapon_can_hit(target.loc.x, target.loc.y)) {
map_window->get_pos(&x, &y, &z);
map_window->moveCursor(target.loc.x - x, target.loc.y - y);
} else {
party->clear_combat_target(actor->id_n == 0 ? 0 : party->get_member_num(actor));
}
break;
default :
break;
}
return;
}
bool Player::attack_select_next_weapon(bool add_newline, bool use_attack_text) {
sint8 i;
for (i = current_weapon + 1; i < ACTOR_MAX_READIED_OBJECTS; i++) {
if (attack_select_weapon_at_location(i, add_newline, use_attack_text) == true)
return true;
}
return false;
}
bool Player::attack_select_weapon_at_location(sint8 location, bool add_newline, bool use_attack_text) {
const CombatType *weapon;
MsgScroll *scroll = Game::get_game()->get_scroll();
if (location == ACTOR_NO_READIABLE_LOCATION) {
current_weapon = location;
if (use_attack_text == false)
return true;
if (add_newline)
scroll->display_string("\n");
if (game_type == NUVIE_GAME_U6 && actor->obj_n == OBJ_U6_SHIP)
scroll->display_string("Attack with ship cannons-");
else
scroll->display_string("Attack with bare hands-");
return true;
}
weapon = actor->get_weapon(location);
if (weapon && weapon->attack > 0) {
current_weapon = location;
if (use_attack_text == false)
return true;
if (add_newline)
scroll->display_string("\n");
scroll->display_fmt_string("Attack with %s-", obj_manager->get_obj_name(weapon->obj_n));
return true;
}
return false;
}
void Player::attack(MapCoord target, Actor *target_actor) {
MsgScroll *scroll = Game::get_game()->get_scroll();
if (weapon_can_hit(target.x, target.y)) {
if (!target_actor)
target_actor = actor_manager->get_actor(target.x, target.y, actor->get_z());
actor->attack(current_weapon, target, target_actor);
if (target_actor) {
party->set_combat_target(actor->id_n == 0 ? 0 : party->get_member_num(actor), target_actor);
} else {
Obj *target_obj = obj_manager->get_obj(target.x, target.y, actor->get_z());
if (target_obj) {
party->set_combat_target(actor->id_n == 0 ? 0 : party->get_member_num(actor), MapCoord(target_obj));
}
}
} else
scroll->display_string("\nOut of range!\n");
//actor_manager->startActors(); // end player turn
return;
}
// Switch to controlling another actor
void Player::update_player(Actor *next_player) {
MsgScroll *scroll = Game::get_game()->get_scroll();
bool same_actor = (next_player == get_actor());
set_actor(next_player); // redirects to ActorManager::set_player()
set_mapwindow_centered(true);
if (!scroll->can_display_prompt() && same_actor)
return;
scroll->display_string("\n");
scroll->display_prompt();
}
/* Rest and repair ship. */
void Player::repairShip() {
MsgScroll *scroll = Game::get_game()->get_scroll();
Actor *ship = get_actor();
if (ship->get_obj_n() != OBJ_U6_SHIP)
return;
// ship actor's health is hull strength
if (ship->get_hp() + 5 > 100) ship->set_hp(100);
else ship->set_hp(ship->get_hp() + 5);
scroll->display_fmt_string("Hull Strength: %d%%\n", ship->get_hp());
Game::get_game()->get_script()->call_advance_time(5);
Game::get_game()->time_changed();
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,173 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_PLAYER_H
#define NUVIE_CORE_PLAYER_H
#include "ultima/nuvie/core/obj_manager.h"
#include "ultima/nuvie/actors/actor.h"
namespace Ultima {
namespace Nuvie {
#define MOVE_COST_USE 5
class Configuration;
class GameClock;
class Actor;
class ActorManager;
class MapWindow;
class Party;
class NuvieIO;
class Player {
const Configuration *config;
int game_type;
GameClock *_clock;
Party *party;
bool party_mode;
bool mapwindow_centered;
Actor *actor;
ActorManager *actor_manager;
ObjManager *obj_manager;
// char name[14]; We can get the name from the player actor. --SB-X
uint8 gender;
uint8 questf;
uint8 karma;
uint8 gargishf; // learned Gargish
uint8 alcohol; // number of alcoholic drinks consumed
MapWindow *map_window;
sint8 current_weapon;
public:
Player(const Configuration *cfg);
bool init(ObjManager *om, ActorManager *am, MapWindow *mw, GameClock *c, Party *p);
void init();
bool load(NuvieIO *objlist);
bool save(NuvieIO *objlist);
Actor *find_actor();
void update_player(Actor *next_player);
bool is_mapwindow_centered() const {
return mapwindow_centered;
}
void set_mapwindow_centered(bool state);
bool is_in_vehicle() const {
return (get_actor()->get_actor_num() == 0);
}
Party *get_party() {
return party;
}
bool set_party_mode(Actor *new_actor);
bool set_solo_mode(Actor *new_actor);
bool in_party_mode() const {
return party_mode;
}
void set_karma(uint8 val) {
karma = val;
}
uint8 get_karma() const {
return karma;
}
void add_karma(uint8 val = 1);
void subtract_karma(uint8 val = 1);
void subtract_movement_points(uint8 points);
void add_alcohol(uint8 val = 1) {
alcohol = clamp_max(alcohol + val, 255);
}
void dec_alcohol(uint8 val = 1) {
if (alcohol > val) {
alcohol -= val;
} else {
alcohol = 0;
}
}
void set_quest_flag(uint8 val) {
questf = val;
}
uint8 get_quest_flag() const {
return questf;
}
void set_gargish_flag(uint8 val) {
gargishf = val;
}
uint8 get_gargish_flag() const {
return gargishf;
}
void set_actor(Actor *new_actor);
Actor *get_actor();
const Actor *get_actor() const;
void get_location(uint16 *ret_x, uint16 *ret_y, uint8 *ret_level) const;
uint8 get_location_level() const;
const char *get_name();
void set_gender(uint8 val) {
gender = val;
}
const char *get_gender_title() const;
uint8 get_gender() const {
return gender;
}
bool check_moveRelative(sint16 rel_x, sint16 rel_y);
void moveRelative(sint16 rel_x, sint16 rel_y, bool mouse_movement = false);
void try_open_door(uint16 x, uint16 y, uint8 z);
void move(sint16 new_x, sint16 new_y, uint8 new_level, bool teleport);
void moveLeft();
void moveRight();
void moveUp();
void moveDown();
void pass();
void repairShip();
uint32 get_walk_delay() const;
bool check_walk_delay();
bool weapon_can_hit(uint16 x, uint16 y);
void attack_select_init(bool use_attack_text = true);
bool attack_select_next_weapon(bool add_newline = false, bool use_attack_text = true);
void attack(MapCoord target, Actor *target_actor);
sint8 get_current_weapon() const {
return current_weapon;
}
protected:
bool attack_select_weapon_at_location(sint8 location, bool add_newline = false, bool use_attack_text = true);
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,947 @@
/* 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/conf/configuration.h"
#include "ultima/nuvie/gui/widgets/console.h"
#include "ultima/nuvie/files/nuvie_io_file.h"
#include "ultima/nuvie/files/nuvie_bmp_file.h"
#include "ultima/nuvie/files/u6_lib_n.h"
#include "ultima/nuvie/files/u6_lzw.h"
#include "ultima/nuvie/core/game.h"
#include "ultima/nuvie/screen/game_palette.h"
#include "ultima/nuvie/screen/dither.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/core/look.h"
#include "ultima/nuvie/core/game_clock.h"
#include "ultima/nuvie/core/tile_manager.h"
#include "ultima/nuvie/gui/gui.h"
namespace Ultima {
namespace Nuvie {
#define NUM_ORIGINAL_TILES 2048
static const char article_tbl[][5] = {"", "a ", "an ", "the "};
static const uint16 U6_ANIM_SRC_TILE[32] = {0x16, 0x16, 0x1a, 0x1a, 0x1e, 0x1e, 0x12, 0x12,
0x1a, 0x1e, 0x16, 0x12, 0x16, 0x1a, 0x1e, 0x12,
0x1a, 0x1e, 0x1e, 0x12, 0x12, 0x16, 0x16, 0x1a,
0x12, 0x16, 0x1e, 0x1a, 0x1a, 0x1e, 0x12, 0x16
};
//static const uint16 U6_WALL_TYPES[1][2] = {{156,176}};
static const Tile gump_cursor = {
0,
false,
false,
false,
false,
false,
true,
false,
false,
0,
//uint8 qty;
//uint8 flags;
0,
0,
0,
{
15, 15, 15, 15, 255, 255, 255, 255, 255, 255, 255, 255, 15, 15, 15, 15,
15, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 15,
15, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 15,
15, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 15,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 15,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
15, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 15,
15, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 15,
15, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 15,
15, 15, 15, 15, 255, 255, 255, 255, 255, 255, 255, 255, 15, 15, 15, 15
}
};
TileManager::TileManager(const Configuration *cfg) : desc_buf(nullptr), config(cfg),
look(nullptr), game_counter(0), rgame_counter(0), extendedTiles(nullptr),
numTiles(NUM_ORIGINAL_TILES) {
memset(tileindex, 0, sizeof(tileindex));
memset(tile, 0, sizeof(tile));
memset(&animdata, 0, sizeof animdata);
config->value("config/GameType", game_type);
}
TileManager::~TileManager() {
// remove tiles
free(desc_buf);
delete look;
if (extendedTiles) {
free(extendedTiles);
}
}
bool TileManager::loadTiles() {
Common::Path maptiles_path, masktype_path, path;
NuvieIOFileRead objtiles_vga;
NuvieIOFileRead tileindx_vga;
NuvieIOFileRead file;
U6Lib_n lib_file;
U6Lzw *lzw;
uint32 tile_offset;
unsigned char *tile_data = nullptr;
uint32 maptiles_size = 0;
uint32 objtiles_size;
unsigned char *masktype = nullptr;
uint32 masktype_size;
uint16 i;
Dither *dither;
dither = Game::get_game()->get_dither();
config_get_path(config, "maptiles.vga", maptiles_path);
config_get_path(config, "masktype.vga", masktype_path);
lzw = new U6Lzw();
switch (game_type) {
case NUVIE_GAME_U6 :
tile_data = lzw->decompress_file(maptiles_path, maptiles_size);
if (tile_data == nullptr) {
ConsoleAddError("Decompressing " + maptiles_path.toString());
return false;
}
masktype = lzw->decompress_file(masktype_path, masktype_size);
if (masktype == nullptr) {
ConsoleAddError("Decompressing " + masktype_path.toString());
return false;
}
break;
case NUVIE_GAME_MD :
case NUVIE_GAME_SE :
if (lib_file.open(maptiles_path, 4, game_type) == false) {
ConsoleAddError("Opening " + maptiles_path.toString());
return false;
}
maptiles_size = lib_file.get_item_size(0);
tile_data = lib_file.get_item(0);
lib_file.close();
if (lib_file.open(masktype_path, 4, game_type) == false) {
ConsoleAddError("Opening " + masktype_path.toString());
return false;
}
//masktype_size = lib_file.get_item_size(0);
masktype = lib_file.get_item(0);
lib_file.close();
break;
}
if (tile_data == nullptr) {
ConsoleAddError("Loading maptiles.vga");
return false;
}
if (masktype == nullptr) {
ConsoleAddError("Loading masktype.vga");
return false;
}
config_get_path(config, "objtiles.vga", path);
if (objtiles_vga.open(path) == false) {
ConsoleAddError("Opening " + path.toString());
return false;
}
objtiles_size = objtiles_vga.get_size();
tile_data = (unsigned char *)nuvie_realloc(tile_data, maptiles_size + objtiles_size);
objtiles_vga.readToBuf(&tile_data[maptiles_size], objtiles_size);
config_get_path(config, "tileindx.vga", path);
if (tileindx_vga.open(path) == false) {
ConsoleAddError("Opening " + path.toString());
return false;
}
for (i = 0; i < 2048; i++) {
tile_offset = tileindx_vga.read2() * 16;
tile[i].tile_num = i;
tile[i].transparent = false;
switch (masktype[i]) {
case U6TILE_TRANS :
tile[i].transparent = true;
memcpy(tile[i].data, &tile_data[tile_offset], 256);
break;
case U6TILE_PLAIN :
memcpy(tile[i].data, &tile_data[tile_offset], 256);
break;
case U6TILE_PBLCK :
tile[i].transparent = true;
decodePixelBlockTile(&tile_data[tile_offset], i);
break;
}
dither->dither_bitmap(tile[i].data, 16, 16, tile[i].transparent);
tileindex[i] = i; //set all tile indexs to default value. this is changed in update() for animated tiles
}
loadAnimData();
loadTileFlag();
free(masktype);
free(tile_data);
look = new Look(config);
if (look->init() == false) {
ConsoleAddError("Initialising Look Class");
return false;
}
desc_buf = (char *)malloc(look->get_max_len() + 6); // add space for "%03d \n\0" or "the \n\0"
if (desc_buf == nullptr) {
ConsoleAddError("Allocating desc_buf");
return false;
}
loadAnimMask();
#ifdef TILEMANAGER_DEBUG
look->print();
DEBUG(0, LEVEL_DEBUGGING, "Dumping tile flags:");
for (i = 0; i < 2048; i++) {
bool plural;
DEBUG(1, LEVEL_DEBUGGING, "%04d : ", i);
print_b(LEVEL_DEBUGGING, tile[i].flags1);
DEBUG(1, LEVEL_DEBUGGING, " ");
print_b(LEVEL_DEBUGGING, tile[i].flags2);
DEBUG(1, LEVEL_DEBUGGING, " ");
print_b(LEVEL_DEBUGGING, tile[i].flags3);
DEBUG(1, LEVEL_DEBUGGING, " %s\n", look->get_description(i, &plural));
}
#endif
delete lzw;
return true;
}
Tile *TileManager::get_tile(uint16 tile_num) {
if (tile_num < NUM_ORIGINAL_TILES) {
return &tile[tileindex[tile_num]];
}
return get_extended_tile(tile_num);
}
Tile *TileManager::get_anim_base_tile(uint16 tile_num) {
return &tile[tileindex[U6_ANIM_SRC_TILE[tile_num - 16] / 2]];
}
Tile *TileManager::get_original_tile(uint16 tile_num) {
if (tile_num < NUM_ORIGINAL_TILES) {
return &tile[tile_num];
}
return get_extended_tile(tile_num);
}
Tile *TileManager::get_extended_tile(uint16 tile_num) {
if (tile_num <= numTiles) {
return &extendedTiles[tile_num - 2048];
}
return &tile[0];
}
// set entry in tileindex[] to tile num
void TileManager::set_tile_index(uint16 tile_index, uint16 tile_num) {
tileindex[tile_index] = tile_num;
}
void TileManager::set_anim_loop(uint16 tile_num, sint8 loopc, uint8 loop) {
for (uint32 i = 0; i < 32; i++)
if (animdata.tile_to_animate[i] == tile_num) {
animdata.loop_count[i] = loopc;
animdata.loop[i] = loop;
}
}
const char *TileManager::lookAtTile(uint16 tile_num, uint16 qty, bool show_prefix) {
const char *desc;
bool plural;
Tile *tileP;
tileP = get_original_tile(tile_num);
if (qty > 1)
plural = true;
else
plural = false;
desc = look->get_description(tileP->tile_num, &plural);
if (show_prefix == false)
return desc;
if (qty > 0 &&
(plural || Game::get_game()->get_game_type() == NUVIE_GAME_SE))
Common::sprintf_s(desc_buf, look->get_max_len() + 6, "%u %s", qty, desc);
else
Common::sprintf_s(desc_buf, look->get_max_len() + 6, "%s%s", article_tbl[tileP->article_n], desc);
DEBUG(0, LEVEL_DEBUGGING, "%s (%x): flags1:", desc_buf, tile_num);
print_b(LEVEL_INFORMATIONAL, tileP->flags1);
DEBUG(1, LEVEL_DEBUGGING, " f2:");
print_b(LEVEL_INFORMATIONAL, tileP->flags2);
DEBUG(1, LEVEL_DEBUGGING, " f3:");
print_b(LEVEL_INFORMATIONAL, tileP->flags3);
DEBUG(1, LEVEL_DEBUGGING, "\n");
return desc_buf;
}
bool TileManager::tile_is_stackable(uint16 tile_num) {
return look->has_plural(tile_num); // luteijn: FIXME, doesn't take into account Zu Ylem, Silver Snake Venom, and possibly other stackables that don't have a plural defined.
}
void TileManager::update() {
uint16 i;
uint16 current_anim_frame = 0;
uint16 prev_tileindex;
uint8 current_hour = Game::get_game()->get_clock()->get_hour();
static sint8 last_hour = -1;
// cycle animated tiles
for (i = 0; i < animdata.number_of_tiles_to_animate; i++) {
if (animdata.loop_count[i] != 0) {
if (animdata.loop[i] == 0) // get next frame
current_anim_frame = (game_counter & animdata.and_masks[i]) >> animdata.shift_values[i];
else if (animdata.loop[i] == 1) // get previous frame
current_anim_frame = (rgame_counter & animdata.and_masks[i]) >> animdata.shift_values[i];
prev_tileindex = tileindex[animdata.tile_to_animate[i]];
tileindex[animdata.tile_to_animate[i]] = tileindex[animdata.first_anim_frame[i] + current_anim_frame];
// loop complete if back to first frame (and not infinite loop)
if (animdata.loop_count[i] > 0
&& tileindex[animdata.tile_to_animate[i]] != prev_tileindex
&& tileindex[animdata.tile_to_animate[i]] == tileindex[animdata.first_anim_frame[i]])
--animdata.loop_count[i];
} else // not animating
tileindex[animdata.tile_to_animate[i]] = tileindex[animdata.first_anim_frame[i]];
}
if (Game::get_game()->anims_paused() == false) { // update counter
if (game_counter == 65535)
game_counter = 0;
else
game_counter++;
if (rgame_counter == 0)
rgame_counter = 65535;
else
rgame_counter--;
}
// cycle time-based animations
if (current_hour != last_hour)
update_timed_tiles(current_hour);
last_hour = current_hour;
}
bool TileManager::loadTileFlag() {
Common::Path filename;
NuvieIOFileRead file;
uint16 i;
config_get_path(config, "tileflag", filename);
if (file.open(filename) == false)
return false;
for (i = 0; i < 2048; i++) {
tile[i].flags1 = file.read1();
if (tile[i].flags1 & 0x2)
tile[i].passable = false;
else
tile[i].passable = true;
if (tile[i].flags1 & 0x1)
tile[i].water = true;
else
tile[i].water = false;
if (tile[i].flags1 & 0x8)
tile[i].damages = true;
else
tile[i].damages = false;
}
for (i = 0; i < 2048; i++) {
tile[i].flags2 = file.read1();
if (tile[i].flags2 & 0x10)
tile[i].toptile = true;
else
tile[i].toptile = false;
if ((tile[i].flags2 & 0x4) || (tile[i].flags2 & 0x8))
tile[i].boundary = true;
else
tile[i].boundary = false;
if (tile[i].flags2 & 0x40)
tile[i].dbl_height = true;
else
tile[i].dbl_height = false;
if (tile[i].flags2 & 0x80)
tile[i].dbl_width = true;
else
tile[i].dbl_width = false;
}
file.seek(0x1400);
for (i = 0; i < 2048; i++) { // '', 'a', 'an', 'the'
tile[i].flags3 = file.read1();
tile[i].article_n = (tile[i].flags3 & 0xC0) >> 6;
}
return true;
}
bool TileManager::loadAnimData() {
Common::Path filename;
NuvieIOFileRead file;
int gameType;
config->value("config/GameType", gameType);
config_get_path(config, "animdata", filename);
if (file.open(filename) == false)
return false;
if (file.get_size() != 194)
return false;
animdata.number_of_tiles_to_animate = file.read2();
for (int i = 0; i < 32; i++) {
animdata.tile_to_animate[i] = file.read2();
}
for (int i = 0; i < 32; i++) {
animdata.first_anim_frame[i] = file.read2();
}
for (int i = 0; i < 32; i++) {
animdata.and_masks[i] = file.read1();
}
for (int i = 0; i < 32; i++) {
animdata.shift_values[i] = file.read1();
}
for (int i = 0; i < 32; i++) { // FIXME: any data on which tiles don't start as animated?
animdata.loop[i] = 0; // loop forwards
if ((gameType == NUVIE_GAME_U6 &&
(animdata.tile_to_animate[i] == 862 // Crank
|| animdata.tile_to_animate[i] == 1009 // Crank
|| animdata.tile_to_animate[i] == 1020)) // Chain
|| (gameType == NUVIE_GAME_MD
&& ((animdata.tile_to_animate[i] >= 1 && animdata.tile_to_animate[i] <= 4) // cistern
|| (animdata.tile_to_animate[i] >= 16 && animdata.tile_to_animate[i] <= 23) // canal
|| (animdata.tile_to_animate[i] >= 616 && animdata.tile_to_animate[i] <= 627) // watch --pu62 lists as 416-427
|| animdata.tile_to_animate[i] == 1992
|| animdata.tile_to_animate[i] == 1993
|| animdata.tile_to_animate[i] == 1980
|| animdata.tile_to_animate[i] == 1981)))
animdata.loop_count[i] = 0; // don't start animated
else
animdata.loop_count[i] = -1; // infinite animation
}
return true;
}
void TileManager::decodePixelBlockTile(const unsigned char *tile_data, uint16 tile_num) {
uint8 len;
uint16 disp;
uint8 x;
const unsigned char *ptr;
unsigned char *data_ptr;
// num_blocks = tile_data[0];
ptr = &tile_data[1];
data_ptr = tile[tile_num].data;
memset(data_ptr, 0xff, 256); //set all pixels to transparent.
for (;;) {
disp = (ptr[0] + (ptr[1] << 8));
x = disp % 160 + (disp >= 1760 ? 160 : 0);
len = ptr[2];
if (len == 0)
break;
data_ptr += x;
memcpy(data_ptr, &ptr[3], len);
data_ptr += len;
ptr += (3 + len);
}
return;
}
bool TileManager::loadAnimMask() {
Common::Path filename;
U6Lzw lzw;
uint16 i;
unsigned char *animmask;
unsigned char *mask_ptr;
uint32 animmask_size;
unsigned char *tile_data;
uint16 bytes2clear;
uint16 displacement;
int gameType;
config->value("config/GameType", gameType);
if (gameType != NUVIE_GAME_U6) //only U6 has animmask.vga
return true;
config_get_path(config, "animmask.vga", filename);
animmask = lzw.decompress_file(filename, animmask_size);
if (animmask == nullptr)
return false;
for (i = 0; i < 32; i++) { // Make the 32 tiles from index 16 onwards transparent with data from animmask.vga
tile_data = tile[16 + i].data;
tile[16 + i].transparent = true;
mask_ptr = animmask + i * 64;
bytes2clear = mask_ptr[0];
if (bytes2clear != 0)
memset(tile_data, 0xff, bytes2clear);
tile_data += bytes2clear;
mask_ptr++;
displacement = mask_ptr[0];
bytes2clear = mask_ptr[1];
mask_ptr += 2;
for (; displacement != 0 && bytes2clear != 0; mask_ptr += 2) {
tile_data += displacement;
memset(tile_data, 0xff, bytes2clear);
tile_data += bytes2clear;
displacement = mask_ptr[0];
bytes2clear = mask_ptr[1];
}
}
free(animmask);
return true;
}
/* Update tiles for timed-based animations.
*/
void TileManager::update_timed_tiles(uint8 hour) {
uint16 new_tile;
if (Game::get_game()->get_game_type() == NUVIE_GAME_U6) {
// sundials
if (hour >= 5 && hour <= 6)
new_tile = 328;
else if (hour >= 7 && hour <= 8)
new_tile = 329;
else if (hour >= 9 && hour <= 10)
new_tile = 330;
else if (hour >= 11 && hour <= 12)
new_tile = 331;
else if (hour >= 13 && hour <= 14)
new_tile = 332;
else if (hour >= 15 && hour <= 16)
new_tile = 333;
else if (hour >= 17 && hour <= 18)
new_tile = 334;
else if (hour >= 19 && hour <= 20)
new_tile = 335;
else // 9pm to 5am
new_tile = 861;
set_tile_index(861, new_tile);
}
}
void TileManager::set_anim_first_frame(uint16 anim_number, uint16 new_start_tile_num) {
if (anim_number < animdata.number_of_tiles_to_animate) {
animdata.first_anim_frame[anim_number] = new_start_tile_num;
}
}
/* Returns tile rotated about the center by `rotate' degrees. (8-bit; clipped to
* standard 16x16 size) It must be deleted after use.
* **Fixed-point rotate function taken from the SDL Graphics Extension library
* (SGE) (c)1999-2003 Anders Lindstr<74>m, licensed under LGPL v2+.**
*/
Tile *TileManager::get_rotated_tile(const Tile *tileP, float rotate, uint8 src_y_offset) {
Tile *new_tile = new Tile(*tileP); // retain properties of original tileP
get_rotated_tile(tileP, new_tile, rotate, src_y_offset);
return new_tile;
}
void TileManager::get_rotated_tile(const Tile *tileP, Tile *dest_tile, float rotate, uint8 src_y_offset) {
unsigned char tile_data[256];
memset(&dest_tile->data, 255, 256); // fill output with transparent color
int32 dy, sx, sy;
int16 rx, ry;
uint16 px = 8, py = 8; // rotate around these coordinates
uint16 xmin = 0, xmax = 15, ymin = 0, ymax = 15; // size
uint16 sxmin = xmin, sxmax = xmax, symin = ymin, symax = ymax;
uint16 qx = 8, qy = 8; // ?? don't remember
float theta = float(rotate * M_PI / 180.0); /* Convert to radians. */
int32 const stx = int32((sin(theta)) * 8192.0);
int32 const ctx = int32((cos(theta)) * 8192.0);
int32 const sty = int32((sin(theta)) * 8192.0);
int32 const cty = int32((cos(theta)) * 8192.0);
int32 const mx = int32(px * 8192.0);
int32 const my = int32(py * 8192.0);
int32 const dx = xmin - qx;
int32 const ctdx = ctx * dx;
int32 const stdx = sty * dx;
int32 const src_pitch = 16;
int32 const dst_pitch = 16;
uint8 const *src_row = (uint8 const *)&tileP->data;
uint8 *dst_pixels = (uint8 *)&dest_tile->data;
uint8 *dst_row;
if (src_y_offset > 0 && src_y_offset < 16) { //shift source down before rotating. This is used by bolt and arrow tiles.
memset(&tile_data, 255, 256);
memcpy(&tile_data[src_y_offset * 16], &tileP->data, 256 - (src_y_offset * 16));
src_row = (uint8 *)&tile_data;
}
for (uint32 y = ymin; y < ymax; y++) {
dy = y - qy;
sx = int32(ctdx + stx * dy + mx); /* Compute source anchor points */
sy = int32(cty * dy - stdx + my);
/* Calculate pointer to dst surface */
dst_row = (uint8 *)dst_pixels + y * dst_pitch;
for (uint32 x = xmin; x < xmax; x++) {
rx = int16(sx >> 13); /* Convert from fixed-point */
ry = int16(sy >> 13);
/* Make sure the source pixel is actually in the source image. */
if ((rx >= sxmin) && (rx <= sxmax) && (ry >= symin) && (ry <= symax))
*(dst_row + x) = *(src_row + ry * src_pitch + rx);
sx += ctx; /* Incremental transformations */
sy -= sty;
}
}
//memcpy(&dest_tile->data, &tile_data, 256); // replace data
return;
}
#if 0 /* old */
Tile *TileManager::get_rotated_tile(Tile *tile, float rotate) {
float angle = (rotate != 0) ? (rotate * M_PI) / 180.0 : 0; // radians
const float mul_x1 = cos(angle); // | x1 y1 |
const float mul_y1 = sin(angle); // | x2 y2 |
const float mul_x2 = -mul_y1;
const float mul_y2 = mul_x1;
unsigned char tile_data[256];
unsigned char *input = (unsigned char *)&tile->data, *output;
memset(&tile_data, 255, 256); // fill output with transparent color
for (sint8 y = -8; y < 8; y++) { // scan input pixels
for (sint8 x = -8; x < 8; x++) {
sint8 rx = (sint8)rint((x * mul_x1) + (y * mul_x2)); // calculate target pixel
sint8 ry = (sint8)rint((x * mul_y1) + (y * mul_y2));
if (rx >= -8 && rx <= 7 && ry >= -8 && ry <= 7) {
output = (unsigned char *)&tile_data;
output += (ry + 8) * 16;
output[rx + 8] = input[x + 8]; // copy
if (rx <= 6) output[rx + 8 + 1] = input[x + 8]; // copy again to adjacent pixel
}
}
input += 16; // next line
}
Tile *new_tile = new Tile(*tile); // retain properties of original tile
memcpy(&new_tile->data, &tile_data, 256); // replace data
return new_tile;
}
#endif
Tile *TileManager::get_cursor_tile() {
Tile *cursor_tile = nullptr;
switch (game_type) {
case NUVIE_GAME_U6 :
cursor_tile = get_tile(365);
break;
case NUVIE_GAME_MD :
cursor_tile = get_tile(265);
break;
case NUVIE_GAME_SE :
cursor_tile = get_tile(381);
break;
}
return cursor_tile;
}
Tile *TileManager::get_use_tile() {
Tile *use_tile = nullptr;
switch (game_type) {
case NUVIE_GAME_U6 :
use_tile = get_tile(364);
break;
case NUVIE_GAME_MD :
use_tile = get_tile(264);
break;
case NUVIE_GAME_SE :
use_tile = get_tile(380);
break;
}
return use_tile;
}
const Tile *TileManager::get_gump_cursor_tile() {
return &gump_cursor;
}
Tile *TileManager::loadCustomTiles(const Common::Path &filename, bool overwrite_tiles, bool copy_tileflags, uint16 tile_num_start_offset) {
NuvieBmpFile bmp;
if (bmp.load(filename) == false) {
return nullptr;
}
unsigned char *tile_data = bmp.getRawIndexedData();
uint16 w = bmp.getWidth();
uint16 h = bmp.getHeight();
uint16 pitch = w;
if (w % 16 != 0 || h % 16 != 0) {
return nullptr;
}
w = w / 16;
h = h / 16;
uint16 num_tiles = w * h;
Tile *newTilePtr = nullptr;
Tile *origTile = nullptr;
if (overwrite_tiles) {
newTilePtr = get_original_tile(tile_num_start_offset);
} else {
newTilePtr = addNewTiles(num_tiles);
}
if (copy_tileflags) {
origTile = get_tile(tile_num_start_offset);
}
Tile *t = newTilePtr;
Dither *dither = Game::get_game()->get_dither();
for (uint16 y = 0; y < h; y++) {
for (uint16 x = 0; x < w; x++) {
unsigned char *data = tile_data + (y * 16 * pitch) + (x * 16);
for (uint16 i = 0; i < 16; i++) {
memcpy(&t->data[i * 16], data, 16);
data += pitch;
}
if (origTile) {
copyTileMetaData(t, origTile);
origTile++;
}
dither->dither_bitmap(t->data, 16, 16, t->transparent);
t++;
}
}
return newTilePtr;
}
void TileManager::copyTileMetaData(Tile *dest, Tile *src) {
dest->passable = src->passable;
dest->water = src->water;
dest->toptile = src->toptile;
dest->dbl_width = src->dbl_width;
dest->dbl_height = src->dbl_height;
dest->transparent = src->transparent;
dest->boundary = src->boundary;
dest->damages = src->damages;
dest->article_n = src->article_n;
dest->flags1 = src->flags1;
dest->flags2 = src->flags2;
dest->flags3 = src->flags3;
}
Tile *TileManager::addNewTiles(uint16 num_tiles) {
Tile *tileDataPtr = (Tile *)realloc(extendedTiles, sizeof(Tile) * (numTiles - NUM_ORIGINAL_TILES + num_tiles));
if (tileDataPtr != nullptr) {
extendedTiles = tileDataPtr;
}
tileDataPtr += (numTiles - NUM_ORIGINAL_TILES);
Tile *t = tileDataPtr;
for (uint16 i = 0; i < num_tiles; i++, t++) {
t->tile_num = numTiles + i;
}
numTiles += num_tiles;
return tileDataPtr;
}
void TileManager::freeCustomTiles() {
if (extendedTiles) {
free(extendedTiles);
extendedTiles = nullptr;
numTiles = NUM_ORIGINAL_TILES;
}
}
void TileManager::exportTilesetToBmpFile(const Common::Path &filename, bool fixupU6Shoreline) {
NuvieBmpFile bmp;
unsigned char pal[256 * 4];
Game::get_game()->get_palette()->loadPaletteIntoBuffer(pal);
//Magic background colour
pal[255 * 4] = 0;
pal[255 * 4 + 1] = 0xdf;
pal[255 * 4 + 2] = 0xfc;
bmp.initNewBlankImage(32 * 16, 64 * 16, pal);
unsigned char *data = bmp.getRawIndexedData();
for (uint8 i = 0; i < 64; i++) {
for (uint8 j = 0; j < 32; j++) {
if (fixupU6Shoreline && game_type == NUVIE_GAME_U6 && (i * 32 + j) >= 16 && (i * 32 + j) < 48) { //lay down the base tile for shoreline tiles
writeBmpTileData(&data[i * 16 * 512 + j * 16], get_anim_base_tile(i * 32 + j), false);
writeBmpTileData(&data[i * 16 * 512 + j * 16], &tile[tileindex[i * 32 + j]], true);
} else {
writeBmpTileData(&data[i * 16 * 512 + j * 16], &tile[tileindex[i * 32 + j]], false);
}
}
}
bmp.save(filename);
}
void TileManager::writeBmpTileData(unsigned char *data, const Tile *t, bool transparent) {
for (uint8 y = 0; y < 16; y++) {
for (uint8 x = 0; x < 16; x++) {
if (!transparent || t->data[y * 16 + x] != 255) {
data[x] = t->data[y * 16 + x];
}
}
data += 512;
}
}
void TileManager::anim_play_repeated(uint8 anim_index) {
if (anim_index < get_number_of_animations()) {
animdata.loop_count[anim_index] = -1; // infinite animation
}
}
void TileManager::anim_stop_playing(uint8 anim_index) {
if (anim_index < get_number_of_animations()) {
animdata.loop_count[anim_index] = 0; // stop animation
}
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,227 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_TILE_MANAGER_H
#define NUVIE_CORE_TILE_MANAGER_H
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/shared/std/string.h"
namespace Ultima {
namespace Nuvie {
class Configuration;
class Look;
// tile types stored in masktype.vga
#define U6TILE_PLAIN 0x0
#define U6TILE_TRANS 0x5
#define U6TILE_PBLCK 0xA
#define TILEFLAG_WALL_MASK 0xf0 // 11110000
//flags1
#define TILEFLAG_WALL_NORTH 0x80
#define TILEFLAG_WALL_EAST 0x40
#define TILEFLAG_WALL_SOUTH 0x20
#define TILEFLAG_WALL_WEST 0x10
#define TILEFLAG_IMPEDANCE (TILEFLAG_WALL_NORTH|TILEFLAG_WALL_EAST|TILEFLAG_WALL_SOUTH|TILEFLAG_WALL_WEST)
#define TILEFLAG_IMPEDANCE_SHIFT 4
#define TILEFLAG_DAMAGING 0x8
#define TILEFLAG_WALL 0x4
#define TILEFLAG_BLOCKING 0x2
#define TILEFLAG_WATER 0x1
//flags2
#define TILEFLAG_DOUBLE_WIDTH 0x80
#define TILEFLAG_DOUBLE_HEIGHT 0x40
#define TILEFLAG_MISSILE_BOUNDARY 0x20
#define TILEFLAG_TOPTILE 0x10
#define TILEFLAG_WINDOW 0x8
#define TILEFLAG_BOUNDARY 0x4
#define TILEFLAG_LIGHT_MSB 0x2
#define TILEFLAG_LIGHT_LSB 0x1
#define GET_TILE_LIGHT_LEVEL(x) (uint8)(x->flags2 & 0x3) // only use with a pointer
//flags3
#define TILEFLAG_ARTICLE_MSB 0x80 // 00 01 10 11
#define TILEFLAG_ARTICLE_LSB 0x40 // - a an the
#define TILEFLAG_UNKNOWN_3_5 0x20
#define TILEFLAG_IGNORE 0x10
#define TILEFLAG_UNKNOWN_3_3 0x8 // Flammable? Mostly_non_metal_object (not affected by acid-slug)?
#define TILEFLAG_FORCED_PASSABLE 0x4
#define TILEFLAG_CAN_PLACE_ONTOP 0x2
#define TILEFLAG_UNKNOWN_LAVA 0x1 // associated with some lava tiles
// FIXME These should probably go else where.
#define TILE_U6_DIRECTION_CURSOR 364
#define TILE_U6_TARGET_CURSOR 365
#define TILE_U6_GREEN_MAGIC 380
#define TILE_U6_PURPLE_MAGIC 381
#define TILE_U6_RED_MAGIC 382
#define TILE_U6_BLUE_MAGIC 383
#define TILE_U6_BLOCKED_EQUIP 389
#define TILE_U6_LIGHTNING 392
#define TILE_U6_FIREBALL 393
#define TILE_U6_SOME_KIND_OF_BLUE_FIELD 394 // ghost's outline for casting/going invis
#define TILE_U6_IS_THIS_AN_ICE_SHOT 395
#define TILE_U6_KILL_SHOT 396
#define TILE_U6_FIRE_SHOT 397
#define TILE_U6_SLING_STONE 398
#define TILE_U6_CANNONBALL 399
#define TILE_U6_EQUIP 410
#define TILE_U6_GARGOYLE_LENS_ANIM_1 440
#define TILE_U6_GARGOYLE_LENS_ANIM_2 441
#define TILE_U6_BRITANNIAN_LENS_ANIM_1 442
#define TILE_U6_BRITANNIAN_LENS_ANIM_2 443
#define TILE_U6_WIZARD_EYE 563
#define TILE_U6_ARROW 566
#define TILE_U6_BOLT 567
#define TILE_SE_BLOCKED_EQUIP 391
#define TILE_SE_EQUIP 392
#define TILE_MD_EQUIP 273
#define TILE_MD_BLOCKED_EQUIP 274
#define TILE_MD_PURPLE_BERRY_MARKER 288
#define TILE_MD_GREEN_BERRY_MARKER 289
#define TILE_MD_BROWN_BERRY_MARKER 290
#define TILE_WIDTH 16
#define TILE_HEIGHT 16
#define TILE_DATA_SIZE 256
typedef struct {
uint16 tile_num;
bool passable;
bool water;
bool toptile;
bool dbl_width;
bool dbl_height;
bool transparent;
bool boundary;
bool damages;
uint8 article_n;
//uint8 qty;
//uint8 flags;
uint8 flags1;
uint8 flags2;
uint8 flags3;
unsigned char data[256];
} Tile;
typedef struct {
uint16 number_of_tiles_to_animate;
uint16 tile_to_animate[0x20];
uint16 first_anim_frame[0x20];
uint8 and_masks[0x20];
uint8 shift_values[0x20];
sint8 loop_count[0x20]; // times to animate (-1 = infinite)
uint8 loop[0x20]; // 0 = loop forwards, 1 = backwards
} Animdata;
class TileManager {
Tile tile[2048];
uint16 tileindex[2048]; //used for animated tiles
uint16 game_counter, rgame_counter;
Animdata animdata;
Look *look;
char *desc_buf; // for look
const Configuration *config;
int game_type;
Tile *extendedTiles;
uint16 numTiles;
public:
TileManager(const Configuration *cfg);
~TileManager();
bool loadTiles();
Tile *get_tile(uint16 tile_num);
Tile *get_anim_base_tile(uint16 tile_num);
Tile *get_original_tile(uint16 tile_num);
void set_tile_index(uint16 tile_index, uint16 tile_num);
uint16 get_tile_index(uint16 tile_index) const {
return tileindex[tile_index];
}
void set_anim_loop(uint16 tile_num, sint8 loopc, uint8 loop = 0);
const char *lookAtTile(uint16 tile_num, uint16 qty, bool show_prefix);
bool tile_is_stackable(uint16 tile_num);
void update();
void update_timed_tiles(uint8 hour);
uint8 get_number_of_animations() const {
return animdata.number_of_tiles_to_animate;
}
uint16 get_numtiles() const {
return numTiles;
}
uint16 get_anim_tile(uint8 anim_index) const {
return anim_index < animdata.number_of_tiles_to_animate ? animdata.tile_to_animate[anim_index] : 0;
}
uint16 get_anim_first_frame(uint8 anim_index) const {
return anim_index < animdata.number_of_tiles_to_animate ? animdata.first_anim_frame[anim_index] : 0;
}
void set_anim_first_frame(uint16 anim_index, uint16 new_start_tile_num);
void anim_play_repeated(uint8 anim_index);
void anim_stop_playing(uint8 anim_index);
Tile *get_rotated_tile(const Tile *tile, float rotate, uint8 src_y_offset = 0);
void get_rotated_tile(const Tile *tile, Tile *dest_tile, float rotate, uint8 src_y_offset = 0);
Tile *get_cursor_tile();
Tile *get_use_tile();
const Tile *get_gump_cursor_tile();
Tile *loadCustomTiles(const Common::Path &filename, bool overwrite_tiles, bool copy_tileflags, uint16 tile_num_start_offset);
void freeCustomTiles();
void exportTilesetToBmpFile(const Common::Path &filename, bool fixupU6Shoreline = true);
protected:
bool loadAnimData();
bool loadTileFlag();
void decodePixelBlockTile(const unsigned char *tile_data, uint16 tile_num);
bool loadAnimMask();
private:
Tile *get_extended_tile(uint16 tile_num);
void copyTileMetaData(Tile *dest, Tile *src);
Tile *addNewTiles(uint16 num_tiles);
void writeBmpTileData(unsigned char *data, const Tile *t, bool transparent);
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,844 @@
/* 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/core/game.h"
#include "ultima/nuvie/actors/actor.h"
#include "ultima/nuvie/gui/widgets/map_window.h"
#include "ultima/nuvie/core/map.h"
#include "ultima/nuvie/core/party.h"
#include "ultima/nuvie/core/events.h"
#include "ultima/nuvie/usecode/usecode.h"
#include "ultima/nuvie/core/u6_objects.h"
#include "ultima/nuvie/actors/u6_work_types.h"
#include "ultima/nuvie/misc/u6_llist.h"
#include "ultima/nuvie/gui/widgets/msg_scroll.h"
#include "ultima/nuvie/core/game_clock.h"
#include "ultima/nuvie/gui/widgets/command_bar.h"
#include "ultima/nuvie/views/view_manager.h"
#include "ultima/nuvie/views/party_view.h"
#include "ultima/nuvie/actors/actor_manager.h"
// FIXME: effects use timers, not the other way around (make a movement effect?)
#include "ultima/nuvie/core/effect_manager.h"
#include "ultima/nuvie/core/effect.h"
#include "ultima/nuvie/core/timed_event.h"
namespace Ultima {
namespace Nuvie {
#define MESG_TIMED CB_TIMED
/* Activate all events for the current time, deleting those that have fired
* and are of no more use. Repeated timers are requeued.
*/
void TimeQueue::call_timers(uint32 now) {
while (!empty() && call_timer(now)) {
}
}
void TimeQueue::clear() {
while (!empty()) {
TimedEvent *event = pop_timer();
if (event->tq_can_delete)
delete event;
}
}
/* Add new timed event to queue, which will activate `event' when time is
* `evtime'.
*/
void TimeQueue::add_timer(TimedEvent *tevent) {
if (tq.empty()) {
tq.push_front(tevent);
return;
}
// in case it's already queued, remove the earlier instance(s)
remove_timer(tevent);
// add after events with earlier/equal time
Std::list<TimedEvent *>::iterator t = tq.begin();
while (t != tq.end() && (*t)->time <= tevent->time) t++;
tq.insert(t, tevent);
}
/* Remove timed event from queue.
*/
void TimeQueue::remove_timer(TimedEvent *tevent) {
Std::list<TimedEvent *>::iterator t = tq.begin();
while (t != tq.end()) {
if (*t == tevent) {
t = tq.erase(t);
} else ++t; // this deletes all duplicates
}
}
/* Remove and return timed event at front of queue, or nullptr if empty.
*/
TimedEvent *TimeQueue::pop_timer() {
TimedEvent *first = nullptr;
if (!empty()) {
first = tq.front();
tq.pop_front(); // remove it
}
return first;
}
/* Call timed event at front of queue, whose time is <= `now'.
* Returns true if an event handler was called. (false if time isn't up yet)
*/
bool TimeQueue::call_timer(uint32 now) {
if (empty())
return false;
TimedEvent *tevent = tq.front();
if (tevent->defunct) {
assert(pop_timer() == tevent);
delete_timer(tevent);
return false;
}
if (tevent->time > now)
return false;
//dequeue event here
pop_timer(); // remove timer in case we have recursion in the timed() call.
tevent->timed(now); // fire
//re-queue if repeating timer.
if (tevent->repeat_count != 0) { // repeat! same delay, add time
// use updated time so it isn't repeated too soon
tevent->set_time();
// tevent->time = _clock->get_ticks() + tevent->delay;
// tevent->time = now + tevent->delay;
add_timer(tevent);
if (tevent->repeat_count > 0) // don't reduce count if infinite (-1)
--tevent->repeat_count;
} else
delete_timer(tevent); // if not repeated, safe to delete
return true;
}
/* Delete a timer, if its can_delete flag is true. Remove from the queue first!
*/
bool TimeQueue::delete_timer(TimedEvent *tevent) {
if (tevent->tq_can_delete) {
delete tevent;
return true;
}
return false;
}
/* Accepts delay, immediate toggle(false), and realtime switch(true). It must
* be queued afterwards to start.
*/
TimedEvent::TimedEvent(uint32 reltime, bool immediate, bool realtime)
: delay(reltime), repeat_count(0), ignore_pause(false),
real_time(realtime), tq_can_delete(true), defunct(false), tq(nullptr) {
if (immediate) // start now (useful if repeat is true)
time = 0;
else
set_time();
}
/* Add myself to the TimeQueue.
*/
void TimedEvent::queue() {
Events *event = Game::get_game()->get_event();
if (tq == nullptr) {
if (real_time)
tq = event->get_time_queue();
else
tq = event->get_game_time_queue();
tq->add_timer(this);
}
}
/* Remove myself from the TimeQueue.
*/
void TimedEvent::dequeue() {
if (tq) {
tq->remove_timer(this);
tq = nullptr;
}
}
/* Add delay to current time and set absolute time.
*/
void TimedEvent::set_time() {
GameClock *_clock = Game::get_game()->get_clock();
time = delay + (real_time ? _clock->get_ticks()
: _clock->get_game_ticks());
}
/*** TimedPartyMove ***/
/* Party movement to/from dungeon or moongate, with a certain number of
* milliseconds between each step.
*/
TimedPartyMove::TimedPartyMove(MapCoord *d, MapCoord *t, uint32 step_delay)
: TimedEvent(step_delay, true) {
init(d, t, nullptr);
}
/* Movement through temporary moongate.
*/
TimedPartyMove::TimedPartyMove(MapCoord *d, MapCoord *t, Obj *use_obj, uint32 step_delay)
: TimedEvent(step_delay, true) {
init(d, t, use_obj);
}
TimedPartyMove::TimedPartyMove(uint32 step_delay)
: TimedEvent(step_delay, true), map_window(nullptr), party(nullptr),
dest(nullptr), target(nullptr), moongate(nullptr), actor_to_hide(nullptr),
moves_left(0), wait_for_effect(0), falling_in(false) {
}
TimedPartyMove::~TimedPartyMove() {
delete dest;
delete target;
}
/* Set destination.
*/
void TimedPartyMove::init(MapCoord *d, MapCoord *t, Obj *use_obj) {
map_window = Game::get_game()->get_map_window();
party = Game::get_game()->get_party();
target = nullptr;
moves_left = party->get_party_size() * 2; // step timeout
wait_for_effect = 0;
actor_to_hide = nullptr;
falling_in = false;
dest = new MapCoord(*d);
if (t)
target = new MapCoord(*t);
moongate = use_obj;
queue(); // start
}
/* Party movement to/from dungeon or to moongate. Repeated until everyone has
* entered, then the entire party is moved to the destination, and this waits
* until the visual effects complete.
*/
void TimedPartyMove::timed(uint32 evtime) {
if (wait_for_effect != 0) { // ignores "falling_in"
repeat(); // repeat once more (callback() must call stop())
return;
}
stop(); // cancelled further down with repeat(), if still moving
if (moves_left) {
if (((falling_in == false) && move_party())
|| ((falling_in == true) && fall_in()))
repeat(); // still moving
} else // timed out, make sure nobody is walking
for (uint32 m = 0; m < party->get_party_size(); m++)
party->get_actor(m)->delete_pathfinder();
// NOTE: set by repeat() or stop()
if (repeat_count == 0) { // everyone is in position
if (falling_in == false) { // change location, get in formation
change_location(); // fade map, move and show party
party->stop_walking(true); // return control (and change viewpoint)
// wait for effect or line up now; Party called unpause_user()
Game::get_game()->pause_user();
if (wait_for_effect == 0) {
delay = 50;
set_time(); // fall-in as fast as possible (but visibly)
moves_left = party->get_party_size() - 1; // followers
falling_in = true;
}
repeat(); // don't stop yet!
} else { // already changed location
Game::get_game()->unpause_user();
stop_timer(); // done
}
}
if (moves_left > 0)
--moves_left;
}
/* Assume the teleport-effect is complete. (don't bother checking msg)
*/
uint16 TimedPartyMove::callback(uint16 msg, CallBack *caller, void *data) {
if (wait_for_effect == 1) { // map-change
wait_for_effect = 0;
Game::get_game()->unpause_anims();
delay = 50;
set_time(); // fall-in as fast as possible (but visibly)
moves_left = party->get_party_size() - 1; // followers
falling_in = true;
} else if (wait_for_effect == 2) { // vanish
wait_for_effect = 0;
Game::get_game()->unpause_anims();
// move_party();
}
return 0;
}
/* Returns true if people are still walking.
*/
bool TimedPartyMove::move_party() {
bool moving = false; // moving or waiting
Actor *used_gate = nullptr; // someone just stepped into the gate (for effect)
if (actor_to_hide) {
hide_actor(actor_to_hide);
moving = true; // allow at least one more tick so we see last actor hide
}
actor_to_hide = nullptr;
for (uint32 a = 0; a < party->get_party_size(); a++) {
Actor *person = party->get_actor(a);
if (person->is_visible()) {
MapCoord loc(person->get_location());
bool really_visible = map_window->in_window(loc.x, loc.y, loc.z);
if (a == 0) // FIXME: should be done automatically, but world is frozen
map_window->centerMapOnActor(person);
if (loc != *dest && really_visible) {
// nobody has just used the gate (who may still be vanishing)
if (!used_gate || loc.distance(*dest) > 1) { // or we aren't close to gate yet
if (!person->get_pathfinder())
person->pathfind_to(*dest); // start (or continue) going to gate
person->update(); // ActorManager is paused
loc = person->get_location(); // don't use the old location
} else
person->delete_pathfinder(); // wait for whoever is at gate
}
if (loc == *dest // actor may have just arrived this turn
|| !really_visible) {
person->delete_pathfinder();
if (moongate) used_gate = person; // hide after this turn
else if (!actor_to_hide) actor_to_hide = person; // hide before next turn
}
moving = true; // even if at gate, might not be hidden yet
}
}
if (used_gate) // wait until now (instead of in loop) so others can catch up before effect
hide_actor(used_gate);
return moving;
}
/* Start a visual effect and hide the party member.
*/
void TimedPartyMove::hide_actor(Actor *person) {
EffectManager *effect_mgr = Game::get_game()->get_effect_manager();
if (wait_for_effect != 2) {
if (moongate) { // vanish
effect_mgr->watch_effect(this, new VanishEffect()); // wait for callback
wait_for_effect = 2;
delay = 1;
set_time(); // effect will be longer than original delay
}
person->hide();
person->move(target->x, target->y, target->z);
}
}
/* Start a visual effect and move the party to the target.
*/
void TimedPartyMove::change_location() {
EffectManager *effect_mgr = Game::get_game()->get_effect_manager();
Graphics::ManagedSurface *mapwindow_capture = nullptr;
if (wait_for_effect != 1) {
bool is_moongate = moongate != nullptr;
if (moongate && moongate->obj_n == OBJ_U6_RED_GATE) { // leave blue moongates
// get image before deleting moongate
mapwindow_capture = map_window->get_sdl_surface();
// must delete moongate here because dest may be the same as target...
// remove moongate before moving so the tempobj cleanup doesn't bite us
Game::get_game()->get_obj_manager()->remove_obj_from_map(moongate);
delete_obj(moongate);
}
if (is_moongate)
Game::get_game()->get_player()->move(target->x, target->y, target->z, true);
else
party->move(target->x, target->y, target->z);
party->show(); // unhide everyone
Game::get_game()->get_view_manager()->update(); // we do this to update party view sun moon display if visible.
if (mapwindow_capture) { // could check this or moongate again
// start fade-to
effect_mgr->watch_effect(this, /* call me */
new FadeEffect(FADE_PIXELATED, FADE_OUT, mapwindow_capture));
delete mapwindow_capture;
Game::get_game()->pause_anims();
wait_for_effect = 1;
}
}
}
/* Pass a few times so everyone in the party can get into formation.
* Returns true if party needs to move more to get into formation. */
bool TimedPartyMove::fall_in() {
bool not_in_position = false; // assume false until someone checks true
party->follow(0, 0);
for (uint8 p = 1; p < party->get_party_size(); p++) {
Actor *follower = party->get_actor(p);
MapCoord at = follower->get_location(),
desired = party->get_formation_coords(p);
follower->update();
if (at != desired)
not_in_position = true;
}
return not_in_position;
}
/*** TimedPartyMoveToVehicle ***/
/* Party movement to vehicle. Second target is unused.
*/
TimedPartyMoveToVehicle::TimedPartyMoveToVehicle(MapCoord *d, Obj *obj,
uint32 step_delay)
: TimedPartyMove(d, nullptr, step_delay) {
ship_obj = obj;
}
/* Repeat until everyone is in the boat, then start it up.
*/
void TimedPartyMoveToVehicle::timed(uint32 evtime) {
stop(); // cancelled further down with repeat(), if still moving
for (uint32 a = 0; a < party->get_party_size(); a++) {
Actor *person = party->get_actor(a);
MapCoord loc(person->get_location());
// not at boat location
if (loc != *dest) {
// offscreen (or timed out), teleport to target
MapWindow *mapWindow = Game::get_game()->get_map_window();
if (!mapWindow->in_window(loc.x, loc.y, loc.z) || moves_left == 0)
person->move(dest->x, dest->y, dest->z, ACTOR_FORCE_MOVE);
else // keep walking
person->pathfind_to(*dest);
person->update();
repeat(); // repeat once more
} else { // at destination
person->delete_pathfinder();
person->hide(); // set in-vehicle
}
}
if (repeat_count == 0) { // everyone is in the boat
Game::get_game()->get_usecode()->use_obj(ship_obj);
party->stop_walking(false); // return control to player
}
if (moves_left > 0)
--moves_left;
}
/* Dump one item at a time out of a container, and print it's name to MsgScroll.
*/
TimedContainerSearch::TimedContainerSearch(Obj *obj) : TimedEvent(500, TIMER_DELAYED) {
Game *game = Game::get_game();
scroll = game->get_scroll();
uc = game->get_usecode();
om = game->get_obj_manager();
container_obj = obj;
prev_obj = nullptr;
//game->set_pause_flags((GamePauseState)(game->get_pause_flags() | PAUSE_USER));
game->pause_user();
queue(); // start
}
void TimedContainerSearch::timed(uint32 evtime) {
prev_obj = uc->get_obj_from_container(container_obj);
if (prev_obj) {
scroll->display_string(om->look_obj(prev_obj, true));
if (container_obj->container->end()) // more objects left
scroll->display_string(container_obj->container->end()->prev
? ", " : ", and ");
repeat();
} else {
Game::get_game()->unpause_user();
stop();
}
}
/*** TimedCallback ***/
TimedCallback::TimedCallback(CallBack *t, void *d, uint32 wait_time, bool repeat)
: TimedEvent(wait_time, TIMER_DELAYED, TIMER_REALTIME) {
set_target(t);
set_user_data(d);
repeat_count = repeat ? -1 : 0;
queue(); // start
}
void TimedCallback::timed(uint32 evtime) {
if (callback_target)
message(MESG_TIMED, &evtime);
else
stop();
}
GameTimedCallback::GameTimedCallback(CallBack *t, void *d, uint32 wait_time, bool repeating)
: TimedCallback(t, d, wait_time, repeating) {
// re-queue timer using game ticks
dequeue();
real_time = TIMER_GAMETIME;
set_time();// change to game time
queue(); // start
}
/*** TimedAdvance: Advance game time by rate until hours has passed. **/
#define TIMEADVANCE_PER_SECOND 1000 /* frequency of timer calls */
TimedAdvance::TimedAdvance(uint8 hours, uint16 r)
: TimedCallback(nullptr, nullptr, 1000 / TIMEADVANCE_PER_SECOND, true),
_clock(Game::get_game()->get_clock()),
minutes_this_hour(0), minutes(0) {
init(hours * 60, r);
}
/* Advance to time indicated by timestring, of the format "HH:MM".
*/
TimedAdvance::TimedAdvance(Std::string timestring, uint16 r)
: TimedCallback(nullptr, nullptr, 1000 / TIMEADVANCE_PER_SECOND, true),
_clock(Game::get_game()->get_clock()),
minutes_this_hour(0), minutes(0) {
uint8 hour = 0, minute = 0;
get_time_from_string(hour, minute, timestring); // set stop time
// set number of hours and minutes to advance
uint16 advance_h = (_clock->get_hour() == hour) ? 24
: (_clock->get_hour() < hour) ? (hour - _clock->get_hour())
: (24 - (_clock->get_hour() - hour));
uint16 advance_m;
if (_clock->get_minute() <= minute)
advance_m = minute - _clock->get_minute();
else {
advance_m = (60 - (_clock->get_minute() - minute));
if (advance_h > 0)
advance_h -= 1;
else
advance_h = 23;
}
// go
init((advance_h * 60) + advance_m, r);
}
/* Set time advance.
*/
void TimedAdvance::init(uint16 min, uint16 r) {
advance = min;
rate = r;
prev_evtime = _clock->get_ticks();
DEBUG(0, LEVEL_DEBUGGING, "TimedAdvance(): %02d:%02d + %02d:%02d (rate=%d)\n",
_clock->get_hour(), _clock->get_minute(), advance / 60, advance % 60, rate);
}
/* Advance game time by rate each second. Timer is stopped after after the time
* has been advanced as requested.
*/
void TimedAdvance::timed(uint32 evtime) {
uint32 milliseconds = (evtime - prev_evtime) > 0 ? (evtime - prev_evtime) : 1;
uint32 fraction = 1000 / milliseconds; // % of second
uint32 minutes_per_fraction = rate / (fraction > 0 ? fraction : 1);
bool hour_passed = false; // another hour has passed
prev_evtime = evtime;
for (uint32 m = 0; m < minutes_per_fraction; m++) {
_clock->inc_minute();
minutes += 1;
if (++minutes_this_hour > 59) {
minutes_this_hour = 0;
hour_passed = true;
}
if (time_passed())
break;
}
Game::get_game()->time_changed();
if (hour_passed && callback_target) // another hour has passed
message(MESG_TIMED, &evtime);
if (time_passed()) {
DEBUG(0, LEVEL_DEBUGGING, "~TimedAdvance(): now %02d:%02d\n", _clock->get_hour(), _clock->get_minute());
if (callback_target && !hour_passed) // make sure to call target
message(MESG_TIMED, &evtime);
stop(); // done
}
}
/* Returns true when the requested amount of time has passed.
*/
bool TimedAdvance::time_passed() const {
return minutes >= advance;
}
/* Set hour and minute from "HH:MM" string.
*/
void TimedAdvance::get_time_from_string(uint8 &hour, uint8 &minute, Std::string timestring) {
char *minute_s = nullptr;
char *hour_s = scumm_strdup(timestring.c_str());
for (uint32 c = 0; c < strlen(hour_s); c++)
if (hour_s[c] == ':') { // get minutes
minute_s = scumm_strdup(&hour_s[c + 1]);
hour_s[c] = '\0';
break;
}
if (hour_s) {
hour = strtol(hour_s, nullptr, 10);
free(hour_s);
}
if (minute_s) {
minute = strtol(minute_s, nullptr, 10);
free(minute_s);
}
}
TimedRestGather::TimedRestGather(uint16 x, uint16 y)
: TimedPartyMove(50) {
MapCoord center = MapCoord(x, y);
init(&center, 0, 0); // set dest to campfire location
Game::get_game()->get_map_window()->updateAmbience();
check_campfire();
}
/* Repeat until everyone is in the circle. */
void TimedRestGather::timed(uint32 evtime) {
stop(); // cancelled further down with repeat(), if still moving
if (moves_left) {
if (move_party())
repeat(); // still moving
} else // timed out, make sure nobody is walking
for (uint32 m = 0; m < party->get_party_size(); m++)
party->get_actor(m)->delete_pathfinder();
if (repeat_count == 0) {
check_campfire();
Game::get_game()->get_event()->rest();
}
if (moves_left > 0)
--moves_left;
}
void TimedRestGather::check_campfire() {
ActorManager *actor_manager = Game::get_game()->get_actor_manager();
for (sint32 a = 0; a < party->get_party_size(); a++) {
Actor *actor = party->get_actor(a);
MapCoord loc = actor->get_location();
if (loc.x == dest->x && loc.y == dest->y) {
for (int x = 0; x < 3; x++)
for (int y = 0; y < 3; y++) {
if (x == 1 && y == 1)
continue;
if (actor_manager->get_actor(dest->x + x - 1, dest->y + y - 1, loc.z) == nullptr) {
actor->move(dest->x + x - 1, dest->y + y - 1, loc.z);
}
}
}
actor->face_location(dest->x, dest->y);
}
}
bool TimedRestGather::move_party() {
bool moving = false; // moving or waiting
const sint16 positions[3 * 3] = {
7, 0, 4, // list of party members arranged by location
3, -1, 2, // campfire is at positions[1][1]
5, 1, 6
};
// check everyone in party because they might not be in the positions list
for (sint32 a = 0; a < party->get_party_size(); a++) {
for (int x = 0; x < 3; x++)
for (int y = 0; y < 3; y++)
if (positions[x + y * 3] == a) {
Actor *actor = party->get_actor(a);
MapCoord loc = actor->get_location();
MapCoord actor_dest(dest->x + x - 1, dest->y + y - 1, loc.z);
if (actor_dest == loc) {
actor->face_location(dest->x, dest->y); // look at camp
actor->delete_pathfinder();
} else {
moving = true; // still moving to circle
if (actor->get_pathfinder() == 0)
actor->pathfind_to(actor_dest.x, actor_dest.y);
actor->set_moves_left(actor->get_dexterity());
actor->update(); // ActorManager is paused
}
x = 3;
y = 3;
break; // break to first loop
}
}
return moving;
}
TimedRest::TimedRest(uint8 hours, Actor *who_will_guard, Obj *campfire_obj)
: TimedAdvance(hours, 80), party(Game::get_game()->get_party()),
scroll(Game::get_game()->get_scroll()), sleeping(0),
print_message(0), lookout(who_will_guard), campfire(campfire_obj),
number_that_had_food(0) {
}
/* This is the only place we know that the TimedAdvance has completed. */
TimedRest::~TimedRest() {
//MapCoord loc = Game::get_game()->get_player()->get_actor()->get_location();
assert(campfire != 0);
campfire->frame_n = 0; // extinguish campfire
bool can_heal = (Game::get_game()->get_clock()->get_rest_counter() == 0); //only heal once every 12 hours.
for (int s = 0; s < party->get_party_size(); s++) {
Actor *actor = party->get_actor(s);
if (can_heal && actor->is_sleeping() && s < number_that_had_food) {
//heal actors.
uint8 hp_diff = actor->get_maxhp() - actor->get_hp();
if (hp_diff > 0) {
if (hp_diff == 1)
hp_diff = 2;
actor->set_hp(actor->get_hp() + NUVIE_RAND() % (hp_diff / 2) + hp_diff / 2);
scroll->display_fmt_string("%s has healed.\n", actor->get_name());
}
}
party->get_actor(s)->revert_worktype(); // "wake up"
}
if (can_heal)
Game::get_game()->get_clock()->set_rest_counter(12); //don't heal by resting for another 12 hours.
Game::get_game()->get_player()->set_mapwindow_centered(true);
Game::get_game()->unpause_user();
Game::get_game()->get_event()->endAction(true); // exit Rest mode
}
void TimedRest::timed(uint32 evtime) {
if (sleeping == false) { // mealtime
if (evtime - prev_evtime > 500) { // print the next message
prev_evtime = evtime; // normally set by TimedAdvance::timed()
if (print_message == 0)
bard_play(); // Iolo plays a tune.
else if (print_message <= party->get_party_size())
eat(party->get_actor(print_message - 1)); // print each person's message
else {
sleeping = true; // finished eating
sleep();
}
++print_message;
}
} else { // sleeping
TimedAdvance::timed(evtime);
for (int s = 0; s < party->get_party_size(); s++)
party->get_actor(s)->update_time(); // checks status effects
// FIXME: chance for random enemies to attack
}
}
/* Check if party has any food, and consume it, allowing the actor to heal. */
void TimedRest::eat(Actor *actor) {
Obj *food = actor->inventory_get_food(); // search actor's inventory first
if (!food)
food = party->get_food();
if (food) {
scroll->display_fmt_string("%s has food.\n", actor->get_name());
Game::get_game()->get_usecode()->destroy_obj(food, 1);
number_that_had_food++;
} else
scroll->display_fmt_string("%s has no food.\n", actor->get_name());
}
/* Look for a bard in the party and have them play a tune. */
void TimedRest::bard_play() {
scroll->display_string("Mealtime!\n");
for (int b = 0; b < party->get_party_size(); b++)
if (party->get_actor(b)->get_obj_n() == OBJ_U6_MUSICIAN) {
Actor *bard = party->get_actor(b);
bard->morph(OBJ_U6_MUSICIAN_PLAYING);
scroll->display_fmt_string("%s plays a tune.\n", bard->get_name());
break;
}
}
/* Start sleeping until the requested time. One person can stand guard. */
void TimedRest::sleep() {
// FIXME: changing to SLEEP worktype should automatically do this
for (int b = 0; b < party->get_party_size(); b++)
if (party->get_actor(b)->get_obj_n() == OBJ_U6_MUSICIAN_PLAYING)
party->get_actor(b)->morph(OBJ_U6_MUSICIAN);
for (int s = 0; s < party->get_party_size(); s++) {
Actor *actor = party->get_actor(s);
if (actor == lookout) {
actor->set_worktype(WORKTYPE_U6_LOOKOUT);
scroll->display_fmt_string("\n%s stands guard while the party rests.\n", actor->get_name());
} else {
actor->set_worktype(WORKTYPE_U6_SLEEP);
}
}
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,291 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_TIMED_EVENT_H
#define NUVIE_CORE_TIMED_EVENT_H
#include "ultima/shared/std/string.h"
#include "ultima/nuvie/misc/call_back.h"
#include "ultima/nuvie/core/obj_manager.h"
namespace Ultima {
namespace Nuvie {
class Actor;
class CallBack;
class Events;
class GameClock;
class MapCoord;
class MapWindow;
class MsgScroll;
class Party;
class TimedCallbackTarget;
class TimedEvent;
/* A queue for our events.
*/
class TimeQueue {
Std::list<TimedEvent *> tq;
public:
TimeQueue() : tq() { }
~TimeQueue() {
clear();
}
bool empty() const {
return tq.empty();
}
void clear();
void add_timer(TimedEvent *tevent);
void remove_timer(TimedEvent *tevent);
TimedEvent *pop_timer();
bool delete_timer(TimedEvent *tevent);
bool call_timer(uint32 now); // activate
void call_timers(uint32 now); // activate all
};
#define TIMER_IMMEDIATE true
#define TIMER_DELAYED false
#define TIMER_REALTIME true
#define TIMER_GAMETIME false
/* Events activated by a timer. Add to one of the Events time-queues to start.
* (THERE IS ONLY ONE SET) The timed() method is called on activation,
* and the timer may be automatically deleted or repeated.
*/
class TimedEvent {
friend class TimeQueue;
friend class Events;
protected:
TimeQueue *tq; // the TimeQueue; so we can add ourself
uint32 delay, time; // timer delay, and next absolute time to activate
sint8 repeat_count; // repeat how many times? (-1=infinite;0=stop)
bool ignore_pause; // activates even if game is paused
bool real_time; // time and delay is in milliseconds (false=game ticks/turns)
bool tq_can_delete; // can TimeQueue delete this TimedEvent when done?
bool defunct; // deleted; don't activate (use to stop timers from outside)
public:
TimedEvent(uint32 reltime, bool immediate = TIMER_DELAYED, bool realtime = TIMER_REALTIME);
virtual ~TimedEvent() { }
virtual void timed(uint32 evtime) {
DEBUG(0, LEVEL_ERROR, "TimedEvent: undefined timer method\n");
}
protected:
// stop repeating, remove from tq if it won't delete it
// NOTE: potential for bug here, this doesn't prevent it from being called once more
void stop() {
repeat_count = 0;
if (!tq_can_delete) dequeue();
}
// repeat once (or for requested count)
void repeat(uint32 count = 1) {
repeat_count = count;
}
public:
void queue(); // set tq, add to tq
void dequeue(); // remove from tq, clear tq
void set_time(); // set `time' from `delay'
void stop_timer() {
stop();
defunct = true;
}
};
/* Print to stdout. (timer test)
*/
class TimedMessage : public TimedEvent {
Std::string msg;
public:
TimedMessage(uint32 reltime, const char *m, bool repeating = false)
: TimedEvent(reltime), msg(m) {
repeat_count = repeating ? -1 : 0;
}
void timed(uint32 evtime) override {
DEBUG(0, LEVEL_NOTIFICATION, "Activate! evtime=%d msg=\"%s\"\n", evtime, msg.c_str());
}
};
/* Move the party to/from a dungeon or ladder or moongate. Characters off-screen
* will teleport.
*/
class TimedPartyMove : public TimedEvent, public CallBack {
protected:
MapWindow *map_window;
Party *party; // the party
MapCoord *dest; // destination, where all actors walk to and disappear
MapCoord *target; // where they reappear at the new plane
uint32 moves_left; // walk timeout
Obj *moongate; // if using a moongate
uint8 wait_for_effect; // waiting for a visual effect to complete if not 0
Actor *actor_to_hide; // this actor has reached exit and should be hidden
bool falling_in;
public:
TimedPartyMove(MapCoord *d, MapCoord *t, uint32 step_delay = 500);
TimedPartyMove(MapCoord *d, MapCoord *t, Obj *use_obj, uint32 step_delay = 500);
TimedPartyMove(uint32 step_delay = 500);
~TimedPartyMove() override;
void init(MapCoord *d, MapCoord *t, Obj *use_obj);
void timed(uint32 evtime) override;
uint16 callback(uint16 msg, CallBack *caller, void *data = nullptr) override;
protected:
bool move_party();
bool fall_in();
void hide_actor(Actor *person);
void change_location();
};
/* Move the party into a vehicle and start it when everyone is there.
*/
class TimedPartyMoveToVehicle : public TimedPartyMove {
Obj *ship_obj; // vehicle center
public:
TimedPartyMoveToVehicle(MapCoord *d, Obj *obj, uint32 step_delay = 125);
void timed(uint32 evtime) override;
};
#if 0
class TimedRTC : public TimedEvent {
public:
TimedRTC() : TimedEvent(1000) {
repeat_count = -1;
}
void timed(uint32 evtime) {
// Game::get_game()->get_player()->pass();
}
};
#endif
//FIXME: It isn't container search. Its a msgscroll effect to print one line at a time.
/* Dump one item at a time out of a container, and print it's name to MsgScroll.
*/
class TimedContainerSearch : public TimedEvent {
MsgScroll *scroll;
UseCode *uc;
ObjManager *om;
Obj *container_obj;
Obj *prev_obj; // removed from container
public:
TimedContainerSearch(Obj *obj);
void timed(uint32 evtime) override;
};
/* Send timer message to callback target after `wait_time' is up, passing it
* some target-defined data.
* new TimedCallback(PowderKeg, (void *)my_powderkeg_data, time_to_explode);
*/
class TimedCallback : public TimedEvent, public CallBack {
public:
TimedCallback(CallBack *t, void *d, uint32 wait_time,
bool repeat = false);
~TimedCallback() override { }
void timed(uint32 evtime) override;
void clear_target() {
set_target(nullptr);
}
};
class GameTimedCallback : public TimedCallback {
public:
GameTimedCallback(CallBack *t, void *d, uint32 wait_time, bool repeat = false);
~GameTimedCallback() override { }
};
/* Advance gameclock up to 24hours from start time. The callback is used every
* hour from the start time, up to and including the stop time.
*/
class TimedAdvance : public TimedCallback {
GameClock *_clock;
uint16 advance; // minutes requested
uint8 minutes_this_hour;
protected:
uint16 minutes; // minutes advanced
uint16 rate; // rate is minutes-per-second
uint32 prev_evtime; // last time the timer was called
public:
TimedAdvance(uint8 hours, uint16 r = 60);
TimedAdvance(Std::string timestring, uint16 r = 60); // "HH:MM"
~TimedAdvance() override { }
void init(uint16 min, uint16 r); // start time advance
void timed(uint32 evtime) override;
bool time_passed() const; // returns true if stop time has passed
void get_time_from_string(uint8 &hour, uint8 &minute, Std::string timestring);
};
/* Camping in the wilderness. Move everyone into a circle and place a campfire
* in the center.
*/
class TimedRestGather : public TimedPartyMove {
public:
TimedRestGather(uint16 x, uint16 y);
void timed(uint32 evtime) override;
protected:
bool move_party();
void check_campfire();
};
/* Camping in the wilderness. Do a TimedAdvance until the requested time. The
* camp can be broken by nearby foes.
*/
class TimedRest : public TimedAdvance {
Party *party;
MsgScroll *scroll;
Actor *lookout;
bool sleeping; // false: mealtime, true: sleeping
uint8 print_message; // which message is to be printed next
Obj *campfire;
uint8 number_that_had_food;
public:
TimedRest(uint8 hours, Actor *lookout, Obj *campfire_obj);
~TimedRest() override;
void timed(uint32 evtime) override;
void eat(Actor *actor);
void bard_play();
void sleep();
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,506 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_U6_OBJECTS_H
#define NUVIE_CORE_U6_OBJECTS_H
namespace Ultima {
namespace Nuvie {
//object numbers
#define OBJ_U6_NOTHING 0
#define OBJ_U6_LEATHER_HELM 1
#define OBJ_U6_CHAIN_COIF 2
#define OBJ_U6_IRON_HELM 3
#define OBJ_U6_SPIKED_HELM 4
#define OBJ_U6_WINGED_HELM 5
#define OBJ_U6_BRASS_HELM 6
#define OBJ_U6_GARGOYLE_HELM 7
#define OBJ_U6_MAGIC_HELM 8
#define OBJ_U6_WOODEN_SHIELD 9
#define OBJ_U6_CURVED_HEATER 10
#define OBJ_U6_WINGED_SHIELD 11
#define OBJ_U6_KITE_SHIELD 12
#define OBJ_U6_SPIKED_SHIELD 13
#define OBJ_U6_BLACK_SHIELD 14
#define OBJ_U6_DOOR_SHIELD 15
#define OBJ_U6_MAGIC_SHIELD 16
#define OBJ_U6_CLOTH_ARMOUR 17
#define OBJ_U6_LEATHER_ARMOR 18
#define OBJ_U6_RING_MAIL 19
#define OBJ_U6_SCALE_MAIL 20
#define OBJ_U6_CHAIN_MAIL 21
#define OBJ_U6_PLATE_MAIL 22
#define OBJ_U6_MAGIC_ARMOUR 23
#define OBJ_U6_SPIKED_COLLAR 24
#define OBJ_U6_GUILD_BELT 25
#define OBJ_U6_GARGOYLE_BELT 26
#define OBJ_U6_LEATHER_BOOTS 27
#define OBJ_U6_SWAMP_BOOTS 28
#define OBJ_U6_TILE_DIRT 29
#define OBJ_U6_TILE_BOARDS 30
#define OBJ_U6_TILE_TILES 31
#define OBJ_U6_TILE_BLUE_TILES 32
#define OBJ_U6_SLING 33
#define OBJ_U6_CLUB 34
#define OBJ_U6_MAIN_GAUCHE 35
#define OBJ_U6_SPEAR 36
#define OBJ_U6_THROWING_AXE 37
#define OBJ_U6_DAGGER 38
#define OBJ_U6_MACE 39
#define OBJ_U6_MORNING_STAR 40
#define OBJ_U6_BOW 41
#define OBJ_U6_CROSSBOW 42
#define OBJ_U6_SWORD 43
#define OBJ_U6_TWO_HANDED_HAMMER 44
#define OBJ_U6_TWO_HANDED_AXE 45
#define OBJ_U6_TWO_HANDED_SWORD 46
#define OBJ_U6_HALBERD 47
#define OBJ_U6_GLASS_SWORD 48
#define OBJ_U6_BOOMERANG 49
#define OBJ_U6_TRIPLE_CROSSBOW 50
#define OBJ_U6_FORCE_FIELD 51
#define OBJ_U6_WIZARD_EYE 52
#define OBJ_U6_OBJECT_53 53
#define OBJ_U6_MAGIC_BOW 54
#define OBJ_U6_ARROW 55
#define OBJ_U6_BOLT 56
#define OBJ_U6_SPELLBOOK 57
#define OBJ_U6_SPELL 58
#define OBJ_U6_CODEX 59
#define OBJ_U6_BOOK_OF_PROPHECIES 60
#define OBJ_U6_BOOK_OF_CIRCLES 61
#define OBJ_U6_VORTEX_CUBE 62
#define OBJ_U6_LOCK_PICK 63
#define OBJ_U6_KEY 64
#define OBJ_U6_BLACK_PEARL 65
#define OBJ_U6_BLOOD_MOSS 66
#define OBJ_U6_GARLIC 67
#define OBJ_U6_GINSENG 68
#define OBJ_U6_MANDRAKE_ROOT 69
#define OBJ_U6_NIGHTSHADE 70
#define OBJ_U6_SPIDER_SILK 71
#define OBJ_U6_SULFUROUS_ASH 72
#define OBJ_U6_MOONSTONE 73
#define OBJ_U6_ANKH_AMULET 74
#define OBJ_U6_SNAKE_AMULET 75
#define OBJ_U6_AMULET_OF_SUBMISSION 76
#define OBJ_U6_GEM 77
#define OBJ_U6_STAFF 78
#define OBJ_U6_LIGHTNING_WAND 79
#define OBJ_U6_FIRE_WAND 80
#define OBJ_U6_STORM_CLOAK 81
#define OBJ_U6_RING 82
#define OBJ_U6_FLASK_OF_OIL 83
#define OBJ_U6_RED_GATE 84
#define OBJ_U6_MOONGATE 85
#define OBJ_U6_GAVEL 86
#define OBJ_U6_ORB_OF_THE_MOONS 87
#define OBJ_U6_GOLD 88
#define OBJ_U6_GOLD_NUGGET 89
#define OBJ_U6_TORCH 90
#define OBJ_U6_ZU_YLEM 91
#define OBJ_U6_SNAKE_VENOM 92
#define OBJ_U6_SEXTANT 93
#define OBJ_U6_SPINNING_WHEEL 94
#define OBJ_U6_GRAPES 95
#define OBJ_U6_BUTTER 96
#define OBJ_U6_GARGISH_VOCABULARY 97
#define OBJ_U6_CHEST 98
#define OBJ_U6_BACKPACK 99
#define OBJ_U6_SCYTHE 100
#define OBJ_U6_PITCHFORK 101
#define OBJ_U6_RAKE 102
#define OBJ_U6_PICK 103
#define OBJ_U6_SHOVEL 104
#define OBJ_U6_HOE 105
#define OBJ_U6_WOODEN_LADDER 106
#define OBJ_U6_YOKE 107
#define OBJ_U6_OVEN_SPATULA 108
#define OBJ_U6_ROLLING_PIN 109
#define OBJ_U6_SPATULA 110
#define OBJ_U6_LADLE 111
#define OBJ_U6_COOKING_SHEET 112
#define OBJ_U6_CLEAVER 113
#define OBJ_U6_KNIFE 114
#define OBJ_U6_WINE 115
#define OBJ_U6_MEAD 116
#define OBJ_U6_ALE 117
#define OBJ_U6_WINE_GLASS 118
#define OBJ_U6_PLATE 119
#define OBJ_U6_MUG 120
#define OBJ_U6_SILVERWARE 121
#define OBJ_U6_CANDLE 122
#define OBJ_U6_MIRROR 123
#define OBJ_U6_TUNIC 124
#define OBJ_U6_HANGER 125
#define OBJ_U6_DRESS 126
#define OBJ_U6_SKILLET 127
#define OBJ_U6_BREAD 128
#define OBJ_U6_MEAT_PORTION 129
#define OBJ_U6_ROLLS 130
#define OBJ_U6_CAKE 131
#define OBJ_U6_CHEESE 132
#define OBJ_U6_HAM 133
#define OBJ_U6_HORSE_CARCASS 134
#define OBJ_U6_HORSE_CHOPS 135
#define OBJ_U6_PANTS 137
#define OBJ_U6_PLANT 138
#define OBJ_U6_DECORATIVE_SWORD 141
#define OBJ_U6_PICTURE 143
#define OBJ_U6_CANDELABRA 145
#define OBJ_U6_PERSON_SLEEPING 146
#define OBJ_U6_CAULDRON 147
#define OBJ_U6_SHIP_DEED 149
#define OBJ_U6_BOOK 151
#define OBJ_U6_SCROLL 152
#define OBJ_U6_PANPIPES 153
#define OBJ_U6_CRYSTAL_BALL 155
#define OBJ_U6_HARPSICHORD 156
#define OBJ_U6_HARP 157
#define OBJ_U6_LUTE 158
#define OBJ_U6_CLOCK 159
#define OBJ_U6_WATER_VASE 161
#define OBJ_U6_BED 163
#define OBJ_U6_FIREPLACE 164
#define OBJ_U6_REMAINS 168
#define OBJ_U6_RUBBER_DUCKY 169
#define OBJ_U6_FUMAROLE 171
#define OBJ_U6_SPIKES 172
#define OBJ_U6_TRAP 173
#define OBJ_U6_SWITCH 174
#define OBJ_U6_ELECTRIC_FIELD 175
#define OBJ_U6_DRAWER 176
#define OBJ_U6_DESK 177
#define OBJ_U6_BUCKET 178
#define OBJ_U6_BUCKET_OF_WATER 179
#define OBJ_U6_BUCKET_OF_MILK 180
#define OBJ_U6_CHURN 181
#define OBJ_U6_BEEHIVE 182
#define OBJ_U6_HONEY_JAR 183
#define OBJ_U6_JAR_OF_HONEY 184
#define OBJ_U6_BARREL 186
#define OBJ_U6_BAG 188
#define OBJ_U6_BASKET 191
#define OBJ_U6_CRATE 192
#define OBJ_U6_PLIERS 203
#define OBJ_U6_HAMMER 204
#define OBJ_U6_BRAZIER 206
#define OBJ_U6_MEAT 209
#define OBJ_U6_RIBS 210
#define OBJ_U6_DEAD_ANIMAL 211
#define OBJ_U6_FAN 212
#define OBJ_U6_MOUSEHOLE 213
#define OBJ_U6_CANNON 221
#define OBJ_U6_POWDER_KEG 223
#define OBJ_U6_THREAD 225
#define OBJ_U6_WELL 233
#define OBJ_U6_FOUNTAIN 234
#define OBJ_U6_SUNDIAL 235
#define OBJ_U6_BELL 236
#define OBJ_U6_RUNE_HONESTY 242
#define OBJ_U6_RUNE_COMPASSION 243
#define OBJ_U6_RUNE_VALOR 244
#define OBJ_U6_RUNE_JUSTICE 245
#define OBJ_U6_RUNE_SACRIFICE 246
#define OBJ_U6_RUNE_HONOR 247
#define OBJ_U6_RUNE_SPIRITUALITY 248
#define OBJ_U6_RUNE_HUMILITY 249
#define OBJ_U6_CHAIR 252
#define OBJ_U6_CAMPFIRE 253
#define OBJ_U6_CROSS 254
#define OBJ_U6_TOMBSTONE 255
#define OBJ_U6_PROTECTION_RING 256
#define OBJ_U6_REGENERATION_RING 257
#define OBJ_U6_INVISIBILITY_RING 258
#define OBJ_U6_FISHING_POLE 264
#define OBJ_U6_FISH 265
#define OBJ_U6_GRAVE 266
#define OBJ_U6_LEVER 268
#define OBJ_U6_DRAWBRIDGE 269
#define OBJ_U6_BALLOON_PLANS 270
#define OBJ_U6_POTION 275
#define OBJ_U6_V_PASSTHROUGH 278
#define OBJ_U6_H_PASSTHROUGH 280
#define OBJ_U6_FENCE 281
#define OBJ_U6_BARS 282
#define OBJ_U6_ROPE 284
#define OBJ_U6_WATER_WHEEL 287
#define OBJ_U6_CRANK 288
#define OBJ_U6_LOG_SAW 289
#define OBJ_U6_CHAIN 293
#define OBJ_U6_XYLOPHONE 296
#define OBJ_U6_OAKEN_DOOR 297
#define OBJ_U6_WINDOWED_DOOR 298
#define OBJ_U6_CEDAR_DOOR 299
#define OBJ_U6_STEEL_DOOR 300
#define OBJ_U6_DOORWAY 301
#define OBJ_U6_LADDER 305
#define OBJ_U6_VOLCANO 307
#define OBJ_U6_HOLE 308
#define OBJ_U6_PORTCULLIS 310
#define OBJ_U6_STONE_LION 312
#define OBJ_U6_FIRE_FIELD 317
#define OBJ_U6_POISON_FIELD 318
#define OBJ_U6_PROTECTION_FIELD 319
#define OBJ_U6_SLEEP_FIELD 320
#define OBJ_U6_CAVE 326
#define OBJ_U6_THRONE 327
#define OBJ_U6_SIGN 332
#define OBJ_U6_SIGN_ARROW 333
#define OBJ_U6_SECRET_DOOR 334
#define OBJ_U6_EGG 335
#define OBJ_U6_CHARGE 336
#define OBJ_U6_EFFECT 337
#define OBJ_U6_BLOOD 338
#define OBJ_U6_DEAD_BODY 339
#define OBJ_U6_DEAD_CYCLOPS 340
#define OBJ_U6_DEAD_GARGOYLE 341
#define OBJ_U6_GIANT_RAT 342
#define OBJ_U6_INSECTS 343
#define OBJ_U6_GIANT_BAT 344
#define OBJ_U6_GIANT_SQUID 345
#define OBJ_U6_REAPER 347
#define OBJ_U6_SEA_SERPENT 346
#define OBJ_U6_SHEEP 348
#define OBJ_U6_DOG 349
#define OBJ_U6_DEER 350
#define OBJ_U6_WOLF 351
#define OBJ_U6_GHOST 352
#define OBJ_U6_GREMLIN 353
#define OBJ_U6_MOUSE 354
#define OBJ_U6_GAZER 355
#define OBJ_U6_BIRD 356
#define OBJ_U6_CORPSER 357
#define OBJ_U6_SNAKE 358
#define OBJ_U6_RABBIT 359
#define OBJ_U6_ROT_WORMS 360
#define OBJ_U6_GIANT_SPIDER 361
#define OBJ_U6_WINGED_GARGOYLE 362
#define OBJ_U6_GARGOYLE 363
#define OBJ_U6_ACID_SLUG 364
#define OBJ_U6_TANGLE_VINE_POD 365
#define OBJ_U6_TANGLE_VINE 366
#define OBJ_U6_DAEMON 367
#define OBJ_U6_SKELETON 368
#define OBJ_U6_DRAKE 369
#define OBJ_U6_HEADLESS 370
#define OBJ_U6_TROLL 371
#define OBJ_U6_MONGBAT 372
#define OBJ_U6_WISP 373
#define OBJ_U6_HYDRA 374
#define OBJ_U6_SLIME 375
#define OBJ_U6_FIGHTER 376
#define OBJ_U6_SWASHBUCKLER 377
#define OBJ_U6_MAGE 378
#define OBJ_U6_VILLAGER 379
#define OBJ_U6_MERCHANT 380
#define OBJ_U6_CHILD 381
#define OBJ_U6_GUARD 382
#define OBJ_U6_JESTER 383
#define OBJ_U6_PEASANT 384
#define OBJ_U6_FARMER 385
#define OBJ_U6_MUSICIAN 386
#define OBJ_U6_WOMAN 387
#define OBJ_U6_CAT 388
#define OBJ_U6_MUSICIAN_PLAYING 392
#define OBJ_U6_SHRINE 393
#define OBJ_U6_BRITANNIAN_LENS 394
#define OBJ_U6_GARGOYLE_LENS 396
#define OBJ_U6_STATUE_OF_MONDAIN 397
#define OBJ_U6_STATUE_OF_MINAX 398
#define OBJ_U6_STATUE_OF_EXODUS 399
#define OBJ_U6_LORD_BRITISH 409
#define OBJ_U6_AVATAR 410
#define OBJ_U6_DRAGON 411
#define OBJ_U6_SHIP 412
#define OBJ_U6_SILVER_SERPENT 413
#define OBJ_U6_SKIFF 414
#define OBJ_U6_RAFT 415
#define OBJ_U6_NO_VEHICLE 416
#define OBJ_U6_QUEST_GATE 416
#define OBJ_U6_DRAGON_EGG 417
#define OBJ_U6_PULL_CHAIN 419
#define OBJ_U6_BALLOON 420
#define OBJ_U6_MAMMOTH_SILK_BAG 421
#define OBJ_U6_BALLOON_BASKET 422
#define OBJ_U6_INFLATED_BALLOON 423
#define OBJ_U6_CYCLOPS 424
#define OBJ_U6_HYDRA_BODY 425
#define OBJ_U6_GIANT_SCORPION 426
#define OBJ_U6_GIANT_ANT 427
#define OBJ_U6_COW 428
#define OBJ_U6_ALLIGATOR 429
#define OBJ_U6_HORSE 430
#define OBJ_U6_HORSE_WITH_RIDER 431
#define OBJ_U6__LAST_ 431
// Savage Empire
#define OBJ_SE_MAGNESIUM_RIBBON 10
#define OBJ_SE_SPEAR 26
#define OBJ_SE_THROWING_AXE 27
#define OBJ_SE_POISONED_DART 36
#define OBJ_SE_RIFLE_BULLET 41
#define OBJ_SE_KNIFE 44
#define OBJ_SE_ARROW 45
#define OBJ_SE_TURTLE_BAIT 47
#define OBJ_SE_FEATHER 48
#define OBJ_SE_CHOCOLATL 54
#define OBJ_SE_PINDE 55
#define OBJ_SE_YOPO 56
#define OBJ_SE_MORTAR 59
#define OBJ_SE_GRINDING_STONE 60
#define OBJ_SE_JUG_OF_PLACHTA 63
#define OBJ_SE_GOLD 69
#define OBJ_SE_GOLD_NUGGET 70
#define OBJ_SE_DIAMOND 72
#define OBJ_SE_EMERALD 73
#define OBJ_SE_RUBY 74
#define OBJ_SE_CORN_MEAL 93
#define OBJ_SE_BOTTLE_OF_LIQUOR 95
#define OBJ_SE_JAR 97
#define OBJ_SE_TORTILLA 102
#define OBJ_SE_MEAT_103 103
#define OBJ_SE_BERRY 104
#define OBJ_SE_CAKE 105
#define OBJ_SE_CORN 108
#define OBJ_SE_BEAN 109
#define OBJ_SE_MEAT_110 110
#define OBJ_SE_ORCHID 115
#define OBJ_SE_PEPPER 120
#define OBJ_SE_SULFUR 123
#define OBJ_SE_CHARCOAL 129
#define OBJ_SE_POTASSIUM_NITRATE 130
#define OBJ_SE_SOFT_CLAY_POT 132
#define OBJ_SE_FIRED_CLAY_POT 133
#define OBJ_SE_CLOTH_STRIP 134
#define OBJ_SE_GRENADE 137
#define OBJ_SE_TAR 139
#define OBJ_SE_WATER 140
#define OBJ_SE_CLOTH 180
#define OBJ_SE_JUG 181
#define OBJ_SE_POUCH 182
#define OBJ_SE_BASKET 183
#define OBJ_SE_POT 184
#define OBJ_SE_TARRED_CLOTH_STRIP 191
#define OBJ_SE_CLAY 192
#define OBJ_SE_GUNPOWDER 204
#define OBJ_SE_BRANCH 206
#define OBJ_SE_TORCH 208
#define OBJ_SE_FLAX 210
#define OBJ_SE_RIB_BONE 211
#define OBJ_SE_CHOP 214
#define OBJ_SE_DEVICE 240
#define OBJ_SE_DEAD_BODY 249
// Martian Dreams
#define OBJ_MD_DOLLAR 24
#define OBJ_MD_PISTOL_ROUND 57
#define OBJ_MD_SHOTGUN_SHELL 58
#define OBJ_MD_RIFLE_ROUND 59
#define OBJ_MD_ELEPHANT_GUN_ROUND 60
#define OBJ_MD_SLING_STONE 63
#define OBJ_MD_ARROW 64
#define OBJ_MD_BERRY 73
#define OBJ_MD_BERRY1 74
#define OBJ_MD_BERRY2 75
#define OBJ_MD_BERRY3 76
#define OBJ_MD_BERRY4 77
#define OBJ_MD_BACKPACK 80
#define OBJ_MD_LARGE_SACK 81
#define OBJ_MD_SMALL_POUCH 82
#define OBJ_MD_BRASS_CHEST 83
#define OBJ_MD_OBSIDIAN_BOX 85
#define OBJ_MD_WOODEN_CRATE 86
#define OBJ_MD_STEAMER_TRUNK 87
#define OBJ_MD_CARPET_BAG 89
#define OBJ_MD_POCKETWATCH 91
#define OBJ_MD_MASONIC_SYMBOL 92
#define OBJ_MD_SPECTACLES 93
#define OBJ_MD_BARREL 104
#define OBJ_MD_MATCH 107
#define OBJ_MD_TORCH 109
#define OBJ_MD_PAGE 122
#define OBJ_MD_CAN_OF_LAMP_OIL 124
#define OBJ_MD_BLOB_OF_OXIUM 131
#define OBJ_MD_RUBLE 132
#define OBJ_MD_LEAD_BOX 139
#define OBJ_MD_WORMSBANE_SEED 158
#define OBJ_MD_CRATE 284
#define OBJ_MD_BAG 285
#define OBJ_MD_BRASS_TRUNK 304
#define OBJ_MD_OXYGENATED_AIR_BOTTLE 324
#define OBJ_MD_DREAMSTUFF 331
#define OBJ_MD_DEAD_BODY 341
#define OBJ_MD_CHIP_OF_RADIUM 449
#define OBJ_MD_DREAM_TELEPORTER 461
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,256 @@
/* 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/conf/configuration.h"
#include "ultima/nuvie/misc/call_back.h"
#include "ultima/nuvie/files/nuvie_io.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/misc/u6_llist.h"
#include "ultima/nuvie/core/weather.h"
#include "ultima/nuvie/core/game.h"
#include "ultima/nuvie/core/game_clock.h"
#include "ultima/nuvie/save/obj_list.h"
#include "ultima/nuvie/core/timed_event.h"
#include "ultima/nuvie/views/view_manager.h"
#include "ultima/nuvie/gui/widgets/map_window.h"
#include "ultima/nuvie/core/map.h"
#include "ultima/nuvie/script/script.h"
namespace Ultima {
namespace Nuvie {
//the longest we will go before having a change in wind direction
#define WEATHER_MAX_WIND 30
Weather::Weather(const Configuration *cfg, GameClock *c, nuvie_game_t type)
: config(cfg), _clock(c), gametype(type), wind_dir(NUVIE_DIR_NONE),
wind_timer(nullptr) {
string s;
config->value(config_get_game_key(config) + "/displayed_wind_dir", s, "from");
if (s == "to")
display_from_wind_dir = false;
else
display_from_wind_dir = true;
}
Weather::~Weather() {
}
bool Weather::load(NuvieIO *objlist) {
clear_wind();
if (gametype == NUVIE_GAME_U6) {
wind_dir = load_wind(objlist);
set_wind_change_callback(); //set a timer to change the wind direction in the future.
send_wind_change_notification_callback();
}
return true;
}
MapCoord Weather::get_moonstone(uint8 moonstone) {
if (moonstone < 8) // FIXME: hardcoded constant
return Game::get_game()->get_script()->call_moonstone_get_loc(moonstone + 1);
DEBUG(0, LEVEL_ERROR, "get_moonstone(%d): Moonstone out of range\n", moonstone);
return MapCoord(0, 0, 0);
}
bool Weather::set_moonstone(uint8 moonstone, MapCoord where) {
if (moonstone < 8) { // FIXME: hardcoded constant
Game::get_game()->get_script()->call_moonstone_set_loc(moonstone + 1, where); //phase starts at 1 in script.
return true;
}
DEBUG(0, LEVEL_ERROR, "set_moonstone(%d): Moonstone out of range\n", moonstone);
return false;
}
void Weather::update_moongates() {
Game::get_game()->get_script()->call_update_moongates(is_moon_visible());
}
NuvieDir Weather::load_wind(NuvieIO *objlist) {
const NuvieDir wind_tbl[8] = {
NUVIE_DIR_N,
NUVIE_DIR_NE,
NUVIE_DIR_E,
NUVIE_DIR_SE,
NUVIE_DIR_S,
NUVIE_DIR_SW,
NUVIE_DIR_W,
NUVIE_DIR_NW
};
objlist->seek(OBJLIST_OFFSET_U6_WIND_DIR);
uint8 objlist_wind = objlist->read1();
if (objlist_wind > 7) //objlist 0xff = Calm 'C'
return NUVIE_DIR_NONE;
return wind_tbl[objlist_wind];
}
void Weather::clear_wind() {
if (wind_timer) {
wind_timer->stop_timer();
wind_timer = nullptr;
}
wind_dir = NUVIE_DIR_NONE;
return;
}
bool Weather::save(NuvieIO *objlist) {
if (gametype == NUVIE_GAME_U6) {
save_wind(objlist);
}
return true;
}
bool Weather::save_wind(NuvieIO *objlist) {
const uint8 wind_tbl[] = {
OBJLIST_U6_WIND_DIR_N,
OBJLIST_U6_WIND_DIR_S,
OBJLIST_U6_WIND_DIR_E,
OBJLIST_U6_WIND_DIR_W,
OBJLIST_U6_WIND_DIR_NE,
OBJLIST_U6_WIND_DIR_SE,
OBJLIST_U6_WIND_DIR_SW,
OBJLIST_U6_WIND_DIR_NW,
OBJLIST_U6_WIND_DIR_C
};
objlist->seek(OBJLIST_OFFSET_U6_WIND_DIR);
objlist->write1(wind_tbl[wind_dir]);
return true;
}
bool Weather::is_eclipse() const {
if (gametype != NUVIE_GAME_U6 || _clock->get_timer(GAMECLOCK_TIMER_U6_ECLIPSE) == 0)
return false;
return true;
}
bool Weather::is_moon_visible() const {
//FIXME this is duplicated logic. Maybe we should look at how the original works out moon locations
uint8 day = _clock->get_day();
uint8 hour = _clock->get_hour();
// trammel (starts 1 hour ahead of sun)
uint8 phase = uint8(nearbyint((day - 1) / TRAMMEL_PHASE)) % 8;
uint8 posA = ((hour + 1) + 3 * phase) % 24; // advance 3 positions each phase-change
if (posA >= 5 && posA <= 19)
return true;
// felucca (starts 1 hour behind sun)
// ...my FELUCCA_PHASE may be wrong but this method works with it...
sint8 phaseb = (day - 1) % uint8(nearbyint(FELUCCA_PHASE * 8)) - 1;
phase = (phaseb >= 0) ? phaseb : 0;
uint8 posB = ((hour - 1) + 3 * phase) % 24; // advance 3 positions per phase-change
if (posB >= 5 && posB <= 19)
return true;
return false;
}
string Weather::get_wind_dir_str() const {
if (display_from_wind_dir) {
static const char from_names[9][3] = {"N", "E", "S", "W", "NE", "SE", "SW", "NW", "C"};
return from_names[wind_dir];
} else {
static const char to_names[9][3] = {"S", "W", "N", "E", "SW", "NW", "NE", "SE", "C"};
return to_names[wind_dir];
}
}
void Weather::change_wind_dir() {
NuvieDir new_wind_dir = static_cast<NuvieDir>(NUVIE_RAND() % 9);
set_wind_dir(new_wind_dir);
return;
}
bool Weather::set_wind_dir(NuvieDir new_wind_dir) {
NuvieDir old_wind_dir = wind_dir;
if (new_wind_dir >= 9)
return false;
clear_wind();
if (Game::get_game()->get_map_window()->in_dungeon_level())
wind_dir = NUVIE_DIR_NONE;
else
wind_dir = new_wind_dir;
if (wind_dir != old_wind_dir)
send_wind_change_notification_callback();
set_wind_change_callback();
return true;
}
inline void Weather::set_wind_change_callback() {
uint16 length = (NUVIE_RAND() % WEATHER_MAX_WIND) + 1;
uint8 *cb_msgid = new uint8;
*cb_msgid = WEATHER_CB_CHANGE_WIND_DIR;
wind_timer = new GameTimedCallback((CallBack *)this, cb_msgid, length);
DEBUG(0, LEVEL_DEBUGGING, "Adding wind change timer. Length = %d\n", length);
}
inline void Weather::send_wind_change_notification_callback() {
for (CallBack *cb : wind_change_notification_list)
cb->callback(WEATHER_CB_CHANGE_WIND_DIR, (CallBack *)this, nullptr);
}
bool Weather::add_wind_change_notification_callback(CallBack *caller) {
wind_change_notification_list.push_back(caller);
return true;
}
uint16 Weather::callback(uint16 msg, CallBack *caller, void *data) {
uint8 *cb_msgid = (uint8 *)callback_user_data;
switch (*cb_msgid) {
case WEATHER_CB_CHANGE_WIND_DIR :
wind_timer = nullptr;
change_wind_dir();
break;
default :
DEBUG(0, LEVEL_ERROR, "Weather: Unknown callback!\n");
break;
}
delete cb_msgid;
return 1;
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,97 @@
/* 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/>.
*
*/
#ifndef NUVIE_CORE_WEATHER_H
#define NUVIE_CORE_WEATHER_H
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/misc/call_back.h"
#include "ultima/nuvie/core/map.h"
namespace Ultima {
namespace Nuvie {
class Configuration;
class NuvieIO;
class CallBack;
class GameClock;
class GameTimedCallback;
using Std::list;
using Std::string;
//our callbacks
#define WEATHER_CB_CHANGE_WIND_DIR 1
#define WEATHER_CB_END_ECLIPSE 2
#define WEATHER_WIND_CALM 8
class Weather: public CallBack {
const Configuration *config;
GameClock *_clock;
nuvie_game_t gametype; // what game is being played?
NuvieDir wind_dir;
Std::list<CallBack *>wind_change_notification_list;
GameTimedCallback *wind_timer;
public:
Weather(const Configuration *cfg, GameClock *c, nuvie_game_t type);
~Weather() override;
bool load(NuvieIO *objlist);
bool save(NuvieIO *objlist);
Std::string get_wind_dir_str() const;
NuvieDir get_wind_dir() const {
return wind_dir;
}
bool is_displaying_from_wind_dir() const {
return display_from_wind_dir;
}
bool set_wind_dir(NuvieDir new_wind_dir);
bool add_wind_change_notification_callback(CallBack *caller);
bool set_moonstone(uint8 moonstone, MapCoord where);
MapCoord get_moonstone(uint8 moonstone);
void update_moongates();
bool is_eclipse() const;
bool is_moon_visible() const;
uint16 callback(uint16 msg, CallBack *caller, void *data = nullptr) override;
protected:
NuvieDir load_wind(NuvieIO *objlist);
bool save_wind(NuvieIO *objlist);
void change_wind_dir();
inline void set_wind_change_callback();
inline void send_wind_change_notification_callback();
void clear_wind();
bool display_from_wind_dir;
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif