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

2
engines/wage/POTFILES Normal file
View File

@@ -0,0 +1,2 @@
engines/wage/metaengine.cpp
engines/wage/saveload.cpp

1054
engines/wage/combat.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
add_engine wage "WAGE" yes "" "" "highres" "imgui"

6
engines/wage/credits.pl Normal file
View File

@@ -0,0 +1,6 @@
begin_section("WAGE");
add_person("Alikhan Balpykov", "Alikhan", "");
add_person("Eugene Sandulenko", "sev", "");
add_person("Alexei Svitkine", "asvitkine", "(Java implementation author)");
end_section();

96
engines/wage/debugger.cpp Normal file
View File

@@ -0,0 +1,96 @@
/* 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 "common/file.h"
#include "wage/wage.h"
#include "wage/debugger.h"
#include "wage/entities.h"
#include "wage/script.h"
#include "wage/world.h"
namespace Wage {
Debugger::Debugger(WageEngine *engine) : GUI::Debugger(), _engine(engine) {
registerCmd("continue", WRAP_METHOD(Debugger, cmdExit));
registerCmd("scenes", WRAP_METHOD(Debugger, Cmd_ListScenes));
registerCmd("script", WRAP_METHOD(Debugger, Cmd_Script));
}
Debugger::~Debugger() {
}
static int strToInt(const char *s) {
if (!*s)
// No string at all
return 0;
else if (toupper(s[strlen(s) - 1]) != 'H')
// Standard decimal string
return atoi(s);
// Hexadecimal string
uint tmp = 0;
int read = sscanf(s, "%xh", &tmp);
if (read < 1)
error("strToInt failed on string \"%s\"", s);
return (int)tmp;
}
bool Debugger::Cmd_ListScenes(int argc, const char **argv) {
int currentScene = 0;
for (uint i = 1; i < _engine->_world->_orderedScenes.size(); i++) { // #0 is STORAGE@
if (_engine->_world->_player->_currentScene == _engine->_world->_orderedScenes[i])
currentScene = i;
debugPrintf("%d: %s\n", i, _engine->_world->_orderedScenes[i]->_name.c_str());
}
debugPrintf("\nCurrent scene is #%d: %s\n", currentScene, _engine->_world->_orderedScenes[currentScene]->_name.c_str());
return true;
}
bool Debugger::Cmd_Script(int argc, const char **argv) {
Script *script = _engine->_world->_player->_currentScene->_script;
if (argc >= 2) {
int scriptNum = strToInt(argv[1]);
if (scriptNum)
script = _engine->_world->_orderedScenes[scriptNum]->_script;
else
script = _engine->_world->_globalScript;
}
if (script == NULL) {
debugPrintf("There is no script for current scene\n");
return true;
}
for (uint i = 0; i < script->_scriptText.size(); i++) {
debugPrintf("%d [%04x]: %s\n", i, script->_scriptText[i]->offset, script->_scriptText[i]->line.c_str());
}
return true;
}
} // End of namespace Wage

46
engines/wage/debugger.h Normal file
View File

@@ -0,0 +1,46 @@
/* 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 WAGE_DEBUGGER_H
#define WAGE_DEBUGGER_H
#include "common/scummsys.h"
#include "gui/debugger.h"
namespace Wage {
class WageEngine;
class Debugger : public GUI::Debugger {
protected:
WageEngine *_engine;
bool Cmd_ListScenes(int argc, const char **argv);
bool Cmd_Script(int argc, const char **argv);
public:
Debugger(WageEngine *engine);
~Debugger() override;
};
} // End of namespace Wage
#endif

541
engines/wage/debugtools.cpp Normal file
View File

@@ -0,0 +1,541 @@
/* 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 "backends/imgui/IconsMaterialSymbols.h"
#define IMGUI_DEFINE_MATH_OPERATORS
#include "backends/imgui/imgui.h"
#include "backends/imgui/imgui_fonts.h"
#include "common/debug.h"
#include "common/system.h"
#include "graphics/managed_surface.h"
#include "wage/wage.h"
#include "wage/dt-internal.h"
#include "wage/design.h"
#include "wage/gui.h"
#include "wage/script.h"
#include "wage/sound.h"
#include "wage/world.h"
namespace Wage {
ImGuiState *_state = nullptr;
ImGuiImage getPatternImage(int fillType) {
int index = fillType - 1;
if (index >= 0 && index < (int)_state->_patternTextures.size()) {
return _state->_patternTextures[index];
}
return {0, 0, 0};
}
void drawThicknessIcon(int thickness) {
ImVec2 pos = ImGui::GetCursorScreenPos();
ImDrawList *dl = ImGui::GetWindowDrawList();
// draw Border
dl->AddRect(pos, pos + ImVec2(16, 16), IM_COL32(255, 255, 255, 255));
float half = thickness * 0.5f;
// draw Bar
dl->AddRectFilled(
ImVec2(pos.x, pos.y + 8 - half),
ImVec2(pos.x + 16, pos.y + 8 + half),
IM_COL32(255, 255, 255, 255));
ImGui::Dummy(ImVec2(16, 16));
ImGui::SetItemTooltip("Line thickness: %d", thickness);
}
void drawMissingPatternIcon() {
ImVec2 pos = ImGui::GetCursorScreenPos();
ImDrawList *dl = ImGui::GetWindowDrawList();
float size = 16.0f;
dl->AddLine(pos, pos + ImVec2(size, size), IM_COL32(255, 0, 0, 255));
dl->AddRect(pos, pos + ImVec2(size, size), IM_COL32(255, 255, 255, 255));
ImGui::Dummy(ImVec2(size, size));
}
ImGuiImage getImageID(Designed *d, const char *type, int steps = -1) {
Common::String key = Common::String::format("%s:%s:%d", d->_name.c_str(), type, steps);
if (_state->_images.contains(key))
return _state->_images[key];
if (!d->_design)
return { 0, 0, 0 };
// We need to precompute dimensions
Graphics::ManagedSurface tmpsurf(1024, 768, Graphics::PixelFormat::createFormatCLUT8());
d->_design->paint(&tmpsurf, *g_wage->_world->_patterns, 0, 0);
int sx = d->_design->getBounds()->width(), sy = d->_design->getBounds()->height();
if (!sx || !sy)
return { 0, 0, 0 };
Graphics::ManagedSurface *surface = new Graphics::ManagedSurface();
surface->create(sx, sy, Graphics::PixelFormat::createFormatCLUT8());
surface->fillRect(Common::Rect(0, 0, sx, sy), 4); // white background
d->_design->paint(surface, *g_wage->_world->_patterns, 0, 0, steps);
if (surface->surfacePtr()) {
_state->_images[key] = { (ImTextureID)g_system->getImGuiTexture(*surface->surfacePtr(), g_wage->_gui->_wm->getPalette(), g_wage->_gui->_wm->getPaletteSize()), sx, sy };
}
delete surface;
return _state->_images[key];
}
void showImage(const ImGuiImage &image, float scale) {
ImVec2 size = { (float)image.width * scale, (float)image.height * scale };
ImGui::BeginGroup();
ImVec2 screenPos = ImGui::GetCursorScreenPos();
ImGui::Image(image.id, size);
ImGui::GetWindowDrawList()->AddRect(screenPos, screenPos + ImVec2(size.x, size.y), 0xFFFFFFFF);
ImGui::EndGroup();
}
void showDesignScriptWindow(Design *d) {
if (!_state->_showScriptWindow)
return;
ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
if (ImGui::Begin("Design Script", &_state->_showScriptWindow)) {
for (uint i = 0; i < d->_drawOps.size(); i++) {
const DrawOp &op = d->_drawOps[i];
// check if this is the current step
bool isCurrent = (_state->_currentStep == i + 1);
if (isCurrent) {
ImVec2 pos = ImGui::GetCursorScreenPos();
float width = ImGui::GetContentRegionAvail().x;
ImDrawList *dl = ImGui::GetWindowDrawList();
ImU32 green = IM_COL32(0, 255, 0, 255);
ImU32 greenTransparent = IM_COL32(0, 255, 0, 50);
// draw Arrow
dl->AddText(pos, green, ICON_MS_ARROW_RIGHT_ALT);
// draw highlight
dl->AddRectFilled(
ImVec2(pos.x + 16.f, pos.y),
ImVec2(pos.x + width, pos.y + 16.f),
greenTransparent,
0.4f);
}
// offset text to the right so it doesn't overlap the arrow
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 16.0f);
// opcode
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "[%5d]", op.offset);
ImGui::SameLine();
// draw current foreground fill pattern
if (op.fillType > 0) {
ImGuiImage patImg = getPatternImage(op.fillType);
if (patImg.id) showImage(patImg, 1.0f);
else drawMissingPatternIcon();
ImGui::SetItemTooltip("Foreground pattern: %d", op.fillType);
ImGui::SameLine();
}
// draw current border fill pattern
if (op.borderFillType > 0) {
ImGuiImage patImg = getPatternImage(op.borderFillType);
if (patImg.id) showImage(patImg, 1.0f);
else drawMissingPatternIcon();
ImGui::SetItemTooltip("Border pattern: %d", op.borderFillType);
ImGui::SameLine();
}
// draw line thickness icon
if (op.borderThickness > 0) {
drawThicknessIcon(op.borderThickness);
ImGui::SameLine();
}
Common::String label = Common::String::format("%s##%d", op.opcode.c_str(), i);
if (ImGui::Selectable(label.c_str(), isCurrent)) {
_state->_currentStep = i + 1;
}
if (isCurrent) {
ImGui::SetItemDefaultFocus();
}
}
}
ImGui::End();
}
void showDesignViewer(Designed *item, const char *typeStr) {
if (!item)
return;
Design *d = item->_design;
if (d) {
if (d->_drawOps.empty()) {
// force render to populate ops
Graphics::ManagedSurface tmp;
tmp.create(1, 1, Graphics::PixelFormat::createFormatCLUT8());
d->paint(&tmp, *g_wage->_world->_patterns, 0, 0, -1);
tmp.free();
}
uint totalSteps = d->_drawOps.size();
if (ImGui::Button(ICON_MS_SKIP_PREVIOUS)) {
if (_state->_currentStep > 1)
_state->_currentStep--;
else
_state->_currentStep = totalSteps;
}
ImGui::SameLine();
if (ImGui::Button(ICON_MS_SKIP_NEXT)) {
if (_state->_currentStep < totalSteps)
_state->_currentStep++;
else
_state->_currentStep = 1;
}
ImGui::SameLine();
if (ImGui::Button("Script")) {
_state->_showScriptWindow = !_state->_showScriptWindow;
}
ImGui::SameLine();
ImGui::Text("Step: %d / %d", (_state->_currentStep), totalSteps);
if (d->getBounds()) {
ImGuiImage imgID = getImageID(item, typeStr, _state->_currentStep);
showImage(imgID, 1.0);
}
showDesignScriptWindow(d);
} else {
ImGui::Text("No Design Data");
}
}
void resetStepToLast(Designed *d) {
if (d && d->_design) {
// if drawOps is empty, force a paint to calculate steps
if (d->_design->_drawOps.empty()) {
Graphics::ManagedSurface tmp(1, 1, Graphics::PixelFormat::createFormatCLUT8());
d->_design->paint(&tmp, *g_wage->_world->_patterns, 0, 0, -1);
}
_state->_currentStep = d->_design->_drawOps.size();
} else {
_state->_currentStep = 1;
}
}
static void showWorld() {
if (!_state->_showWorld)
return;
// Calculate the viewport size
ImVec2 viewportSize = ImGui::GetMainViewport()->Size;
// Calculate the window size
ImVec2 windowSize = ImVec2(
viewportSize.x * 0.9f,
viewportSize.y * 0.9f
);
// Calculate the centered position
ImVec2 centeredPosition = ImVec2(
(viewportSize.x - windowSize.x) * 0.5f,
(viewportSize.y - windowSize.y) * 0.5f
);
// Set the next window position and size
ImGui::SetNextWindowPos(centeredPosition, ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(windowSize, ImGuiCond_FirstUseEver);
if (ImGui::Begin("World", &_state->_showWorld)) {
ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_None;
if (ImGui::BeginTabBar("MainTabBar", tab_bar_flags)) {
if (ImGui::BeginTabItem("Scenes")) {
{ // Left pane
ImGui::BeginChild("ChildL", ImVec2(ImGui::GetContentRegionAvail().x * 0.4f, ImGui::GetContentRegionAvail().y), ImGuiChildFlags_None);
if (ImGui::BeginListBox("##listbox scenes", ImVec2(-FLT_MIN, ImGui::GetContentRegionAvail().y))) {
for (int n = 0; n < (int)g_wage->_world->_orderedScenes.size(); n++) {
const bool is_selected = (_state->_selectedScene == n);
Common::String label = Common::String::format("%s##%d", g_wage->_world->_orderedScenes[n]->_name.c_str(), n);
if (ImGui::Selectable(label.c_str(), is_selected)) {
_state->_selectedScene = n;
resetStepToLast(g_wage->_world->_orderedScenes[n]);
}
// Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
if (is_selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndListBox();
}
ImGui::EndChild();
}
ImGui::SameLine();
{ // Right pane
ImGui::BeginChild("ChildR", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y), ImGuiChildFlags_Borders);
if (ImGui::BeginTabBar("SceneTabBar", tab_bar_flags)) {
if (ImGui::BeginTabItem("Script")) {
if (g_wage->_world->_orderedScenes[_state->_selectedScene]->_script && g_wage->_world->_orderedScenes[_state->_selectedScene]->_script->_scriptText.size()) {
for (auto &t : g_wage->_world->_orderedScenes[_state->_selectedScene]->_script->_scriptText) {
ImGui::Text("[%4d]", t->offset);
ImGui::SameLine();
ImGui::Text("%s", t->line.c_str());
}
} else {
ImGui::Text("No script");
}
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Text")) {
ImGui::TextWrapped("%s", g_wage->_world->_orderedScenes[_state->_selectedScene]->_text.c_str());
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Design")) {
Designed *d = g_wage->_world->_orderedScenes[_state->_selectedScene];
showDesignViewer(d, "scene");
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
ImGui::EndChild();
}
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Objects")) {
{ // Left pane
ImGui::BeginChild("ChildL", ImVec2(ImGui::GetContentRegionAvail().x * 0.4f, ImGui::GetContentRegionAvail().y), ImGuiChildFlags_None);
if (ImGui::BeginListBox("##listbox objects", ImVec2(-FLT_MIN, ImGui::GetContentRegionAvail().y))) {
for (int n = 0; n < (int)g_wage->_world->_orderedObjs.size(); n++) {
const bool is_selected = (_state->_selectedObj == n);
Common::String label = Common::String::format("%s##%d", g_wage->_world->_orderedObjs[n]->_name.c_str(), n);
if (ImGui::Selectable(label.c_str(), is_selected)) {
_state->_selectedObj = n;
resetStepToLast(g_wage->_world->_orderedObjs[n]);
}
// Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
if (is_selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndListBox();
}
ImGui::EndChild();
}
ImGui::SameLine();
{ // Right pane
ImGui::BeginChild("ChildR", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y), ImGuiChildFlags_Borders);
showDesignViewer(g_wage->_world->_orderedObjs[_state->_selectedObj], "obj");
ImGui::EndChild();
}
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Characters")) {
{ // Left pane
ImGui::BeginChild("ChildL", ImVec2(ImGui::GetContentRegionAvail().x * 0.4f, ImGui::GetContentRegionAvail().y), ImGuiChildFlags_None);
if (ImGui::BeginListBox("##listbox characters", ImVec2(-FLT_MIN, ImGui::GetContentRegionAvail().y))) {
for (int n = 0; n < (int)g_wage->_world->_orderedChrs.size(); n++) {
const bool is_selected = (_state->_selectedChr == n);
Common::String label = Common::String::format("%s##%d", g_wage->_world->_orderedChrs[n]->_name.c_str(), n);
if (ImGui::Selectable(label.c_str(), is_selected)) {
_state->_selectedChr = n;
resetStepToLast(g_wage->_world->_orderedChrs[n]);
}
// Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
if (is_selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndListBox();
}
ImGui::EndChild();
}
ImGui::SameLine();
{ // Right pane
ImGui::BeginChild("ChildR", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y), ImGuiChildFlags_Borders);
showDesignViewer(g_wage->_world->_orderedChrs[_state->_selectedChr], "chr");
ImGui::EndChild();
}
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Sounds")) {
{ // Left pane
ImGui::BeginChild("ChildL", ImVec2(ImGui::GetContentRegionAvail().x * 0.4f, ImGui::GetContentRegionAvail().y), ImGuiChildFlags_None);
if (ImGui::BeginListBox("##listbox sounds", ImVec2(-FLT_MIN, ImGui::GetContentRegionAvail().y))) {
for (int n = 0; n < (int)g_wage->_world->_orderedSounds.size(); n++) {
const bool is_selected = (_state->_selectedSound == n);
Common::String label = Common::String::format("%s##%d", g_wage->_world->_orderedSounds[n]->_name.c_str(), n);
if (ImGui::Selectable(label.c_str(), is_selected))
_state->_selectedSound = n;
// Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
if (is_selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndListBox();
}
ImGui::EndChild();
}
ImGui::SameLine();
{ // Right pane
ImGui::BeginChild("ChildR", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y), ImGuiChildFlags_Borders);
ImGui::Text("Sound playback");
ImGui::EndChild();
}
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Global Script")) {
for (auto &t : g_wage->_world->_globalScript->_scriptText) {
ImGui::Text("[%4d]", t->offset);
ImGui::SameLine();
ImGui::Text("%s", t->line.c_str());
}
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("World")) {
ImGui::Text("This is the Global Script tab!\nblah blah blah blah blah");
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
}
ImGui::End();
}
void onImGuiInit() {
ImGuiIO &io = ImGui::GetIO();
io.Fonts->AddFontDefault();
ImFontConfig icons_config;
icons_config.MergeMode = true;
icons_config.PixelSnapH = false;
icons_config.OversampleH = 3;
icons_config.OversampleV = 3;
icons_config.GlyphOffset = {0, 4};
static const ImWchar icons_ranges[] = {ICON_MIN_MS, ICON_MAX_MS, 0};
ImGui::addTTFFontFromArchive("MaterialSymbolsSharp.ttf", 16.f, &icons_config, icons_ranges);
_state = new ImGuiState();
// pre-load patterns into array
Graphics::MacPatterns &patterns = *g_wage->_world->_patterns;
for (uint i = 0; i < patterns.size(); ++i) {
Graphics::ManagedSurface surface(16, 16, Graphics::PixelFormat::createFormatCLUT8());
Common::Rect rect(16, 16);
Design::drawFilledRect(&surface, rect, kColorBlack, patterns, i + 1);
if (surface.surfacePtr()) {
ImTextureID tex = (ImTextureID)g_system->getImGuiTexture(
*surface.surfacePtr(),
g_wage->_gui->_wm->getPalette(),
g_wage->_gui->_wm->getPaletteSize());
_state->_patternTextures.push_back({tex, 16, 16});
} else {
_state->_patternTextures.push_back({0, 0, 0});
}
}
}
void onImGuiRender() {
if (!debugChannelSet(-1, kDebugImGui)) {
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange | ImGuiConfigFlags_NoMouse;
return;
}
if (!_state)
return;
ImGui::GetIO().ConfigFlags &= ~(ImGuiConfigFlags_NoMouseCursorChange | ImGuiConfigFlags_NoMouse);
if (ImGui::BeginMainMenuBar()) {
if (ImGui::BeginMenu("View")) {
ImGui::MenuItem("World", NULL, &_state->_showWorld);
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
showWorld();
}
void onImGuiCleanup() {
delete _state;
_state = nullptr;
}
} // namespace Wage

31
engines/wage/debugtools.h Normal file
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/>.
*
*/
#ifndef WAGE_DEBUGTOOLS_H
#define WAGE_DEBUGTOOLS_H
namespace Wage {
void onImGuiInit();
void onImGuiRender();
void onImGuiCleanup();
} // namespace Wage
#endif // WAGE_DEBUGTOOLS_H

759
engines/wage/design.cpp Normal file
View File

@@ -0,0 +1,759 @@
/* 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/>.
*
* MIT License:
*
* Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#include "graphics/managed_surface.h"
#include "graphics/primitives.h"
#include "graphics/macgui/macwindowmanager.h"
#include "wage/design.h"
namespace Wage {
struct PlotData {
Graphics::ManagedSurface *surface;
Graphics::MacPatterns *patterns;
uint fillType;
int thickness;
Design *design;
PlotData(Graphics::ManagedSurface *s, Graphics::MacPatterns *p, int f, int t, Design *d) :
surface(s), patterns(p), fillType(f), thickness(t), design(d) {}
};
void drawPixelPlain(int x, int y, int color, void *data);
Design::Design(Common::SeekableReadStream *data) {
_len = data->readUint16BE() - 2;
_data = (byte *)malloc(_len);
data->read(_data, _len);
_surface = NULL;
_bounds = new Common::Rect();
_maskImage = nullptr;
_boundsCalculationMode = false;
_renderedSteps = 0;
_lastOpString = "";
}
Design::~Design() {
free(_data);
if (_surface)
_surface->free();
delete _surface;
delete _bounds;
delete _maskImage;
}
void Design::paint(Graphics::ManagedSurface *surface, Graphics::MacPatterns &patterns, int x, int y, int steps) {
bool needRender = false;
if (_surface == NULL) {
_boundsCalculationMode = true;
_bounds->debugPrint(4, "Internal bounds:");
render(patterns, -1);
_boundsCalculationMode = false;
if (_bounds->right == -10000) {
_bounds->left = _bounds->top = _bounds->right = _bounds->bottom = 0;
}
_bounds->debugPrint(4, "Calculated bounds:");
_surface = new Graphics::ManagedSurface;
_surface->create(_bounds->width() + 1, _bounds->height(), Graphics::PixelFormat::createFormatCLUT8());
_surface->clear(kColorGreen);
needRender = true;
}
_bounds->debugPrint(4, "Using bounds:");
#if 0
PlotData pd(_surface, &patterns, 8, 1, this);
int x1 = 50, y1 = 50, x2 = 200, y2 = 200, borderThickness = 30;
Common::Rect inn(x1-5, y1-5, x2+5, y2+5);
drawRoundRect(inn, 6, kColorGray80, false, drawPixelPlain, &pd);
drawThickLine(x1, y1, x2-borderThickness, y1, borderThickness, kColorBlack, drawPixel, &pd);
drawThickLine(x2-borderThickness, y1, x2-borderThickness, y2, borderThickness, kColorBlack, drawPixel, &pd);
drawThickLine(x2-borderThickness, y2-borderThickness, x1, y2-borderThickness, borderThickness, kColorBlack, drawPixel, &pd);
drawThickLine(x1, y2-borderThickness, x1, y1, borderThickness, kColorBlack, drawPixel, &pd);
drawThickLine(x2+10, y2+10, x2+100, y2+100, borderThickness, kColorBlack, drawPixel, &pd);
g_system->copyRectToScreen(_surface->getPixels(), _surface->pitch, 0, 0, _surface->w, _surface->h);
while (true) {
((WageEngine *)g_engine)->processEvents();
g_system->updateScreen();
g_system->delayMillis(50);
}
return;
#endif
if (_drawOps.empty())
render(patterns);
// only re-render if the requested step count changed
if (needRender || _renderedSteps != steps) {
_surface->clear(kColorGreen);
render(patterns, steps);
_renderedSteps = steps;
}
if (_bounds->width() && _bounds->height()) {
const int padding = 14;
// Common::Rect from(padding, padding, _bounds->width() - 2 * padding, _bounds->height() - 2 * padding);
// in order to restore the design, we just cut the left and top part of that
Common::Rect from(padding, padding, _bounds->width(), _bounds->height());
Common::Rect to(from);
to.moveTo(x, y);
surface->transBlitFrom(*_surface, from, to, kColorGreen);
}
}
void Design::render(Graphics::MacPatterns &patterns, int steps) {
Common::MemoryReadStream in(_data, _len);
bool needRender = true;
int currentStep = 0;
while (needRender) {
uint32 opOffset = in.pos();
byte fillType = in.readByte();
byte borderThickness = in.readByte();
byte borderFillType = in.readByte();
int type = in.readByte();
if (in.eos())
break;
debug(8, "fill: %d borderFill: %d border: %d type: %d", fillType, borderFillType, borderThickness, type);
switch (type) {
case 4:
drawRect(_surface, in, patterns, fillType, borderThickness, borderFillType);
break;
case 8:
drawRoundRect(_surface, in, patterns, fillType, borderThickness, borderFillType);
break;
case 12:
drawOval(_surface, in, patterns, fillType, borderThickness, borderFillType);
break;
case 16:
case 20:
drawPolygon(_surface, in, patterns, fillType, borderThickness, borderFillType);
break;
case 24:
drawBitmap(_surface, in);
break;
default:
warning("Unknown type => %d", type);
break;
}
if (_boundsCalculationMode && !_lastOpString.empty()) {
DrawOp op;
op.offset = opOffset;
op.fillType = fillType;
op.borderThickness = borderThickness;
op.borderFillType = borderFillType;
op.opcode = _lastOpString;
_drawOps.push_back(op);
_lastOpString = "";
}
currentStep++;
if (steps > 0 && currentStep >= steps) {
needRender = false;
break;
}
//g_system->copyRectToScreen(_surface->getPixels(), _surface->pitch, 0, 0, _surface->w, _surface->h);
//((WageEngine *)g_engine)->processEvents();
//g_system->updateScreen();
//g_system->delayMillis(500);
}
}
bool Design::isInBounds(int x, int y) {
if (_surface == NULL)
error("Design::isInBounds(): Surface is null");
if (_maskImage == nullptr)
return false;
if (x >= _maskImage->w || y >= _maskImage->h)
return false;
byte pixel = ((byte *)_maskImage->getBasePtr(x, y))[0];
return pixel != kColorGreen;
}
void Design::adjustBounds(int16 x, int16 y) {
_bounds->right = MAX(x, _bounds->right);
_bounds->bottom = MAX(y, _bounds->bottom);
}
class PlotDataPrimitives : public Graphics::Primitives {
void drawPoint(int x, int y, uint32 color, void *data) override {
PlotData *p = (PlotData *)data;
if (p->fillType > p->patterns->size())
return;
if (p->design && p->design->isBoundsCalculation()) {
if (x < 0 || y < 0)
return;
if (p->thickness == 1) {
p->design->adjustBounds(x, y);
} else {
int x1 = x;
int x2 = x1 + p->thickness;
int y1 = y;
int y2 = y1 + p->thickness;
for (y = y1; y < y2; y++)
for (x = x1; x < x2; x++)
p->design->adjustBounds(x, y);
}
return;
}
const byte *pat = p->patterns->operator[](p->fillType - 1);
if (p->thickness == 1) {
if (x >= 0 && x < p->surface->w && y >= 0 && y < p->surface->h) {
uint xu = (uint)x; // for letting compiler optimize it
uint yu = (uint)y;
*((byte *)p->surface->getBasePtr(xu, yu)) =
(pat[yu % 8] & (1 << (7 - xu % 8))) ?
color : (byte)kColorWhite;
}
} else {
int x1 = x - p->thickness / 2;
int x2 = x1 + p->thickness;
int y1 = y - p->thickness / 2;
int y2 = y1 + p->thickness;
for (y = y1; y < y2; y++)
for (x = x1; x < x2; x++)
if (x >= 0 && x < p->surface->w && y >= 0 && y < p->surface->h) {
uint xu = (uint)x; // for letting compiler optimize it
uint yu = (uint)y;
*((byte *)p->surface->getBasePtr(xu, yu)) =
(pat[yu % 8] & (1 << (7 - xu % 8))) ?
color : (byte)kColorWhite;
}
}
}
};
class PlotDataCirclePrimitives : public Graphics::Primitives {
void drawPoint(int x, int y, uint32 color, void *data) override {
PlotData *p = (PlotData *)data;
if (p->fillType > p->patterns->size())
return;
if (p->design && p->design->isBoundsCalculation()) {
if (x < 0 || y < 0)
return;
if (p->thickness == 1) {
p->design->adjustBounds(x, y);
} else {
int x1 = x;
int x2 = x1 + p->thickness;
int y1 = y;
int y2 = y1 + p->thickness;
for (y = y1; y < y2; y++)
for (x = x1; x < x2; x++)
p->design->adjustBounds(x, y);
}
return;
}
const byte *pat = p->patterns->operator[](p->fillType - 1);
// Draw circle when thickness is > 1, put a pixel otherwise
if (p->thickness == 1) {
if (x >= 0 && x < p->surface->w && y >= 0 && y < p->surface->h) {
uint xu = (uint)x; // for letting compiler optimize it
uint yu = (uint)y;
*((byte *)p->surface->getBasePtr(xu, yu)) =
(pat[yu % 8] & (1 << (7 - xu % 8))) ? color : (byte)kColorWhite;
}
} else {
int x1 = x - p->thickness / 2;
int x2 = x1 + p->thickness;
int y1 = y - p->thickness / 2;
int y2 = y1 + p->thickness;
PlotData pd(p->surface, p->patterns, p->fillType, 1, p->design);
subprimitives.drawEllipse(x1, y1, x2 - 1, y2 - 1, kColorBlack, true, &pd);
}
}
private:
PlotDataPrimitives subprimitives;
};
void drawPixelPlain(int x, int y, int color, void *data) {
PlotData *p = (PlotData *)data;
if (p->design && p->design->isBoundsCalculation()) {
p->design->adjustBounds(x, y);
return;
}
if (x >= 0 && x < p->surface->w && y >= 0 && y < p->surface->h)
*((byte *)p->surface->getBasePtr(x, y)) = (byte)color;
}
void Design::drawRect(Graphics::ManagedSurface *surface, Common::ReadStream &in,
Graphics::MacPatterns &patterns, byte fillType, byte borderThickness, byte borderFillType) {
int16 y1 = in.readSint16BE();
int16 x1 = in.readSint16BE();
int16 y2 = in.readSint16BE();
int16 x2 = in.readSint16BE();
if (x1 > x2)
SWAP(x1, x2);
if (y1 > y2)
SWAP(y1, y2);
if (_boundsCalculationMode)
_lastOpString = Common::String::format("rect %d, %d, %d, %d", x1, y1, x2, y2);
if (_boundsCalculationMode) {
_bounds->top = MIN(y1, _bounds->top);
_bounds->left = MIN(x1, _bounds->left);
_bounds->right = MAX(x2, _bounds->right);
_bounds->bottom = MAX(y2, _bounds->bottom);
}
if (_surface) {
if (!_maskImage) {
_maskImage = new Graphics::ManagedSurface(_surface->w, _surface->h);
_maskImage->clear(kColorGreen);
}
_maskImage->fillRect(Common::Rect(x1, y1, x2, y2), kColorBlack);
}
Common::Rect r(x1, y1, x2, y2);
PlotData pd(surface, &patterns, fillType, 1, this);
PlotDataPrimitives primitives;
if (fillType <= patterns.size())
primitives.drawFilledRect1(r, kColorBlack, &pd);
pd.fillType = borderFillType;
pd.thickness = borderThickness;
if (borderThickness > 1) {
x1 += borderThickness / 2;
y1 += borderThickness / 2;
x2 -= (borderThickness - 1) / 2;
y2 -= (borderThickness - 1) / 2;
}
if (borderThickness > 0 && borderFillType <= patterns.size()) {
primitives.drawLine(x1, y1, x2 - 1, y1, kColorBlack, &pd);
primitives.drawLine(x2 - 1, y1, x2 - 1, y2 - 1, kColorBlack, &pd);
primitives.drawLine(x2 - 1, y2 - 1, x1, y2 - 1, kColorBlack, &pd);
primitives.drawLine(x1, y2 - 1, x1, y1, kColorBlack, &pd);
}
}
void Design::drawRoundRect(Graphics::ManagedSurface *surface, Common::ReadStream &in,
Graphics::MacPatterns &patterns, byte fillType, byte borderThickness, byte borderFillType) {
int16 y1 = in.readSint16BE();
int16 x1 = in.readSint16BE();
int16 y2 = in.readSint16BE() - 1;
int16 x2 = in.readSint16BE() - 1;
int16 arc = in.readSint16BE();
if (x1 > x2)
SWAP(x1, x2);
if (y1 > y2)
SWAP(y1, y2);
if (_boundsCalculationMode)
_lastOpString = Common::String::format("roundRect %d, %d, %d, %d", x1, y1, x2, y2);
if (_surface) {
if (!_maskImage) {
_maskImage = new Graphics::ManagedSurface(_surface->w, _surface->h);
_maskImage->clear(kColorGreen);
}
_maskImage->fillRect(Common::Rect(x1, y1, x2, y2), kColorBlack);
}
if (borderThickness > 1) {
x1 += borderThickness / 2;
y1 += borderThickness / 2;
x2 -= (borderThickness - 1) / 2;
y2 -= (borderThickness - 1) / 2;
}
Common::Rect r(x1, y1, x2, y2);
PlotData pd(surface, &patterns, fillType, 1, this);
PlotDataPrimitives primitives;
if (fillType <= patterns.size())
primitives.drawRoundRect1(r, arc / 2, kColorBlack, true, &pd);
pd.fillType = borderFillType;
pd.thickness = borderThickness;
if (borderThickness > 0 && borderFillType <= patterns.size())
primitives.drawRoundRect1(r, arc / 2 - 1, kColorBlack, false, &pd);
}
void Design::drawPolygon(Graphics::ManagedSurface *surface, Common::ReadStream &in,
Graphics::MacPatterns &patterns, byte fillType, byte borderThickness, byte borderFillType) {
byte ignored = in.readSint16BE(); // ignored
if (ignored)
warning("Ignored: %d", ignored);
int numBytes = in.readSint16BE(); // #bytes used by polygon data, including the numBytes
int16 by1 = in.readSint16BE();
int16 bx1 = in.readSint16BE();
int16 by2 = in.readSint16BE();
int16 bx2 = in.readSint16BE();
Common::Rect bbox(bx1, by1, bx2, by2);
if (_surface) {
if (!_maskImage) {
_maskImage = new Graphics::ManagedSurface(_surface->w, _surface->h);
_maskImage->clear(kColorGreen);
}
_maskImage->fillRect(Common::Rect(bx1, by1, bx2, by2), kColorBlack);
}
numBytes -= 8;
int y1 = in.readSint16BE();
int x1 = in.readSint16BE();
Common::Array<int> xcoords;
Common::Array<int> ycoords;
numBytes -= 6;
while (numBytes > 0) {
int y2 = y1;
int x2 = x1;
int b = in.readSByte();
if (b == -128) {
y2 = in.readSint16BE();
numBytes -= 3;
} else {
y2 += b;
numBytes -= 1;
}
b = in.readSByte();
if (b == -128) {
x2 = in.readSint16BE();
numBytes -= 3;
} else {
x2 += b;
numBytes -= 1;
}
xcoords.push_back(x1);
ycoords.push_back(y1);
x1 = x2;
y1 = y2;
}
xcoords.push_back(x1);
ycoords.push_back(y1);
if (_boundsCalculationMode) {
Common::String vertices;
for (uint i = 0; i < xcoords.size(); ++i) {
vertices += Common::String::format("(%d,%d)", xcoords[i], ycoords[i]);
if (i < xcoords.size() - 1)
vertices += ", ";
}
_lastOpString = Common::String::format("polygon %d, %d, %d, %d, [%s]", bx1, by1, bx2, by2, vertices.c_str());
}
if (borderThickness > 1) {
for (uint i = 0; i < xcoords.size(); ++i) {
xcoords[i] += borderThickness / 2;
ycoords[i] += borderThickness / 2;
}
}
int npoints = xcoords.size();
int *xpoints = (int *)calloc(npoints, sizeof(int));
int *ypoints = (int *)calloc(npoints, sizeof(int));
for (int i = 0; i < npoints; i++) {
xpoints[i] = xcoords[i];
ypoints[i] = ycoords[i];
}
PlotData pd(surface, &patterns, fillType, 1, this);
PlotDataPrimitives primitives;
if (fillType <= patterns.size()) {
primitives.drawPolygonScan(xpoints, ypoints, npoints, bbox, kColorBlack, &pd);
}
pd.fillType = borderFillType;
pd.thickness = borderThickness;
if (borderThickness > 0 && borderFillType <= patterns.size()) {
for (int i = 1; i < npoints; i++)
primitives.drawLine(xpoints[i-1], ypoints[i-1], xpoints[i], ypoints[i], kColorBlack, &pd);
}
free(xpoints);
free(ypoints);
}
void Design::drawOval(Graphics::ManagedSurface *surface, Common::ReadStream &in,
Graphics::MacPatterns &patterns, byte fillType, byte borderThickness, byte borderFillType) {
int16 y1 = in.readSint16BE();
int16 x1 = in.readSint16BE();
int16 y2 = in.readSint16BE();
int16 x2 = in.readSint16BE();
PlotData pd(surface, &patterns, fillType, 1, this);
PlotDataPrimitives primitives;
if (_boundsCalculationMode)
_lastOpString = Common::String::format("oval %d, %d, %d, %d", x1, y1, x2, y2);
if (_surface) {
if (!_maskImage) {
_maskImage = new Graphics::ManagedSurface(_surface->w, _surface->h);
_maskImage->clear(kColorGreen);
}
_maskImage->fillRect(Common::Rect(x1, y1, x2, y2), kColorBlack);
}
if (fillType <= patterns.size())
PlotDataPrimitives().drawEllipse(x1, y1, x2, y2, kColorBlack, true, &pd);
pd.fillType = borderFillType;
pd.thickness = borderThickness;
if (borderThickness > 1) {
x1 += borderThickness / 2;
y1 += borderThickness / 2;
x2 -= (borderThickness - 1) / 2;
y2 -= (borderThickness - 1) / 2;
}
if (borderThickness > 0 && borderFillType <= patterns.size())
PlotDataCirclePrimitives().drawEllipse(x1, y1, x2 - 1, y2 - 1, kColorBlack, false, &pd);
}
void Design::drawBitmap(Graphics::ManagedSurface *surface, Common::SeekableReadStream &in) {
int numBytes = in.readSint16BE();
// Check for broken bitmap data
if (numBytes < 10) {
if (numBytes > 2) {
warning("Design::drawBitmap(): Broken bitmap data");
in.seek(numBytes - 2, SEEK_CUR);
} else {
warning("Design::drawBitmap(): Completely broken bitmap data");
}
return;
}
int y1 = in.readSint16BE();
int x1 = in.readSint16BE();
int y2 = in.readSint16BE();
int x2 = in.readSint16BE();
int w = x2 - x1;
int h = y2 - y1;
if (_boundsCalculationMode)
_lastOpString = Common::String::format("bitmap %d, %d, %d, %d [%d bytes]", x1, y1, x2, y2, numBytes);
if (_surface) {
if (!_maskImage) {
_maskImage = new Graphics::ManagedSurface(_surface->w, _surface->h);
_maskImage->clear(kColorGreen);
}
}
Graphics::Surface tmp;
tmp.create(w, h, Graphics::PixelFormat::createFormatCLUT8());
numBytes -= 10;
int x = 0, y = 0;
while (numBytes > 0 && y < h) {
int n = in.readSByte();
int count;
int b = 0;
int state = 0;
numBytes--;
if ((n >= 0) && (n <= 127)) { // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally.
count = n + 1;
state = 1;
} else if ((n >= -127) && (n <= -1)) { // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times.
b = in.readByte();
numBytes--;
count = -n + 1;
state = 2;
} else { // Else if n is -128, noop.
count = 0;
}
for (int i = 0; i < count && y < h; i++) {
byte color = 0;
if (state == 1) {
color = in.readByte();
numBytes--;
} else if (state == 2)
color = b;
for (int c = 0; c < 8; c++) {
if (_boundsCalculationMode) {
adjustBounds(x1 + x, y1 + y);
} else if (x1 + x >= 0 && x1 + x < surface->w && y1 + y >= 0 && y1 + y < surface->h)
*((byte *)tmp.getBasePtr(x, y)) = (color & (1 << (7 - c % 8))) ? kColorBlack : kColorWhite;
x++;
if (x == w) {
y++;
x = 0;
break;
}
}
if (numBytes < 0 && (i + 1 < count) && (y < h)) {
warning("Design::drawBitmap(): Bitmap data ended prematurely");
break;
}
}
}
if (numBytes > 0)
in.seek(numBytes, SEEK_CUR);
else if (numBytes < 0)
warning("Design::drawBitmap(): Read past the end of bitmap data (%d bytes)", numBytes);
if (!_boundsCalculationMode) {
Graphics::FloodFill ff(&tmp, kColorWhite, kColorGreen);
for (int yy = 0; yy < h; yy++) {
ff.addSeed(0, yy);
ff.addSeed(w - 1, yy);
}
for (int xx = 0; xx < w; xx++) {
ff.addSeed(xx, 0);
ff.addSeed(xx, h - 1);
}
ff.fill();
y = 0;
if (y1 < 0)
y = -y1;
for (; y < h && y1 + y < surface->h; y++) {
x = 0;
if (x1 < 0)
x = -x1;
byte *src = (byte *)tmp.getBasePtr(x, y);
byte *dst = (byte *)surface->getBasePtr(x1 + x, y1 + y);
byte *mask = (byte *)_maskImage->getBasePtr(x1 + x, y1 + y);
for (; x < w && x1 + x < surface->w; x++) {
if (*src != kColorGreen) {
*dst = *src;
*mask = kColorBlack;
}
src++;
dst++;
mask++;
}
}
}
tmp.free();
}
void Design::drawRect(Graphics::ManagedSurface *surface, const Common::Rect &rect, int thickness, int color, Graphics::MacPatterns &patterns, byte fillType) {
PlotData pd(surface, &patterns, fillType, thickness, nullptr);
PlotDataPrimitives().drawRect1(rect, kColorBlack, &pd);
}
void Design::drawRect(Graphics::ManagedSurface *surface, int x1, int y1, int x2, int y2, int thickness, int color, Graphics::MacPatterns &patterns, byte fillType) {
drawRect(surface, Common::Rect(MIN(x1, x2), MIN(y1, y2), MAX(x1, x2), MAX(y1, y2)),
thickness, color, patterns, fillType);
}
void Design::drawFilledRect(Graphics::ManagedSurface *surface, Common::Rect &rect, int color, Graphics::MacPatterns &patterns, byte fillType) {
PlotData pd(surface, &patterns, fillType, 1, nullptr);
PlotDataPrimitives().drawFilledRect1(rect, color, &pd);
}
void Design::drawFilledRoundRect(Graphics::ManagedSurface *surface, Common::Rect &rect, int arc, int color, Graphics::MacPatterns &patterns, byte fillType) {
PlotData pd(surface, &patterns, fillType, 1, nullptr);
PlotDataPrimitives().drawRoundRect1(rect, arc, color, true, &pd);
}
void Design::drawHLine(Graphics::ManagedSurface *surface, int x1, int x2, int y, int thickness, int color, Graphics::MacPatterns &patterns, byte fillType) {
PlotData pd(surface, &patterns, fillType, thickness, nullptr);
PlotDataPrimitives().drawHLine(x1, x2, y, color, &pd);
}
void Design::drawVLine(Graphics::ManagedSurface *surface, int x, int y1, int y2, int thickness, int color, Graphics::MacPatterns &patterns, byte fillType) {
PlotData pd(surface, &patterns, fillType, thickness, nullptr);
PlotDataPrimitives().drawVLine(x, y1, y2, color, &pd);
}
} // End of namespace Wage

122
engines/wage/design.h Normal file
View File

@@ -0,0 +1,122 @@
/* 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/>.
*
* MIT License:
*
* Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#ifndef WAGE_DESIGN_H
#define WAGE_DESIGN_H
#include "common/memstream.h"
#include "common/rect.h"
#include "graphics/nine_patch.h"
#include "graphics/macgui/macwindowmanager.h"
namespace Wage {
using namespace Graphics::MacGUIConstants;
// struct to hold design operation data
struct DrawOp {
uint32 offset;
Common::String opcode;
int fillType;
int borderThickness;
int borderFillType;
int lineSize;
};
class Design {
public:
Design(Common::SeekableReadStream *data);
~Design();
Common::Array<DrawOp> _drawOps;
void setBounds(Common::Rect *bounds) {
_bounds = bounds;
}
Common::Rect *getBounds() {
return _bounds;
}
void paint(Graphics::ManagedSurface *canvas, Graphics::MacPatterns &patterns, int x, int y, int steps = -1);
bool isInBounds(int x, int y);
static void drawRect(Graphics::ManagedSurface *surface, const Common::Rect &rect, int thickness, int color, Graphics::MacPatterns &patterns, byte fillType);
static void drawRect(Graphics::ManagedSurface *surface, int x1, int y1, int x2, int y2, int thickness, int color, Graphics::MacPatterns &patterns, byte fillType);
static void drawFilledRect(Graphics::ManagedSurface *surface, Common::Rect &rect, int color, Graphics::MacPatterns &patterns, byte fillType);
static void drawFilledRoundRect(Graphics::ManagedSurface *surface, Common::Rect &rect, int arc, int color, Graphics::MacPatterns &patterns, byte fillType);
static void drawHLine(Graphics::ManagedSurface *surface, int x1, int x2, int y, int thickness, int color, Graphics::MacPatterns &patterns, byte fillType);
static void drawVLine(Graphics::ManagedSurface *surface, int x, int y1, int y2, int thickness, int color, Graphics::MacPatterns &patterns, byte fillType);
bool isBoundsCalculation() { return _boundsCalculationMode; }
void adjustBounds(int16 x, int16 y);
private:
byte *_data;
int _len;
int _renderedSteps;
Common::String _lastOpString;
Common::Rect *_bounds;
Graphics::ManagedSurface *_surface;
bool _boundsCalculationMode;
Graphics::ManagedSurface *_maskImage;
private:
void render(Graphics::MacPatterns &patterns, int steps = -1);
void drawRect(Graphics::ManagedSurface *surface, Common::ReadStream &in,
Graphics::MacPatterns &patterns, byte fillType, byte borderThickness, byte borderFillType);
void drawRoundRect(Graphics::ManagedSurface *surface, Common::ReadStream &in,
Graphics::MacPatterns &patterns, byte fillType, byte borderThickness, byte borderFillType);
void drawPolygon(Graphics::ManagedSurface *surface, Common::ReadStream &in,
Graphics::MacPatterns &patterns, byte fillType, byte borderThickness, byte borderFillType);
void drawOval(Graphics::ManagedSurface *surface, Common::ReadStream &in,
Graphics::MacPatterns &patterns, byte fillType, byte borderThickness, byte borderFillType);
void drawBitmap(Graphics::ManagedSurface *surface, Common::SeekableReadStream &in);
};
} // End of namespace Wage
#endif

137
engines/wage/detection.cpp Normal file
View File

@@ -0,0 +1,137 @@
/* 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 "base/plugins.h"
#include "common/file.h"
#include "common/macresman.h"
#include "engines/advancedDetector.h"
static const PlainGameDescriptor wageGames[] = {
{"afm", "Another Fine Mess"},
{"amot", "A Mess O' Trouble"},
{"cantitoe", "Camp Cantitoe"},
{"drakmythcastle", "Drakmyth Castle"},
{"grailquest", "GrailQuest: Adventure in the Age of King Arthur"},
{"raysmaze", "Ray's Maze"},
{"scepters", "Enchanted Scepters"},
{"twisted", "Twisted!"},
{"worldbuilder", "World Builder"},
{"wage", "WAGE"},
{0, 0}
};
#include "wage/detection_tables.h"
#include "wage/detection.h"
static const DebugChannelDef debugFlagList[] = {
{ Wage::kDebugImGui, "imgui", "Show ImGui debug window (if available)"},
{ Wage::kDebugSound, "sound", "Show sound debug information"},
{ Wage::kDebugLoading, "loading", "Show loading debug information" },
DEBUG_CHANNEL_END
};
static ADGameDescription s_fallbackDesc = {
"wage",
"",
AD_ENTRY1(0, 0),
Common::EN_ANY,
Common::kPlatformMacintosh,
ADGF_NO_FLAGS,
GUIO0()
};
class WageMetaEngineDetection : public AdvancedMetaEngineDetection<ADGameDescription> {
mutable Common::String _filenameStr;
public:
WageMetaEngineDetection() : AdvancedMetaEngineDetection(Wage::gameDescriptions, wageGames) {
_md5Bytes = 2 * 1024 * 1024;
_guiOptions = GUIO4(GUIO_NOSPEECH, GUIO_NOMIDI, GAMEOPTION_TTS, GUIO_NOMUSIC);
}
const char *getName() const override {
return "wage";
}
const char *getEngineName() const override {
return "World Adventure Game Engine";
}
const char *getOriginalCopyright() const override {
return "World Builder (C) Silicon Beach Software";
}
const DebugChannelDef *getDebugChannels() const override {
return debugFlagList;
}
ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist, ADDetectedGameExtraInfo **extra) const override;
};
ADDetectedGame WageMetaEngineDetection::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist, ADDetectedGameExtraInfo **extra) const {
SearchMan.addDirectory("WageMetaEngineDetection::fallbackDetect", fslist.begin()->getParent());
for (Common::FSList::const_iterator fs = fslist.begin(); fs != fslist.end(); ++fs) {
if (fs->isDirectory())
continue;
Common::Path filePath = Common::Path(fs->getPathInArchive());
Common::MacResManager resManager;
if (!resManager.open(filePath)) {
continue;
}
Common::MacFinderInfo finderInfo;
if (resManager.getFileFinderInfo(filePath, finderInfo)) {
if (READ_BE_UINT32(finderInfo.type) != MKTAG('A', 'P', 'P', 'L')) {
continue;
}
if (READ_BE_UINT32(finderInfo.creator) != MKTAG('W', 'E', 'D', 'T')) {
continue;
}
Common::Path outPath(fs->getFileName());
_filenameStr = outPath.toString();
s_fallbackDesc.filesDescriptions[0].fileName = _filenameStr.c_str();
ADDetectedGame game;
game.desc = &s_fallbackDesc;
FileProperties tmp;
if (getFileProperties(allFiles, kMD5MacResFork, filePath, tmp)) {
game.hasUnknownFiles = true;
game.matchedFiles[filePath] = tmp;
}
SearchMan.remove("WageMetaEngineDetection::fallbackDetect");
return game;
}
}
SearchMan.remove("WageMetaEngineDetection::fallbackDetect");
return ADDetectedGame();
}
REGISTER_PLUGIN_STATIC(WAGE_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, WageMetaEngineDetection);

27
engines/wage/detection.h Normal file
View File

@@ -0,0 +1,27 @@
/* 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 WAGE_DETECTION_H
#define WAGE_DETECTION_H
#define GAMEOPTION_TTS GUIO_GAMEOPTIONS1
#endif // WAGE_DETECTION_H

View File

@@ -0,0 +1,391 @@
/* 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 "engines/wage/wage.h"
namespace Wage {
#define ADGF_DEFAULT (ADGF_DROPLANGUAGE|ADGF_DROPPLATFORM|ADGF_MACRESFORK)
#define ADGF_GENERIC (ADGF_DEFAULT|ADGF_USEEXTRAASTITLE|ADGF_AUTOGENTARGET)
#define FANGAME(n,m,s) { "wage",n,AD_ENTRY1s(n,m,s),Common::EN_ANY,Common::kPlatformMacintosh,ADGF_GENERIC,GUIO0() }
#define FANGAMEr(n,m,s,r) { "wage",n,AD_ENTRY1s(n,m,s),Common::EN_ANY,Common::kPlatformMacintosh,ADGF_GENERIC | r,GUIO0() }
#define FANGAMEN(n,f,m,s) { "wage",n,AD_ENTRY1s(f,m,s),Common::EN_ANY,Common::kPlatformMacintosh,ADGF_GENERIC,GUIO0() }
#define FANGAMENr(n,f,m,s,r) { "wage",n,AD_ENTRY1s(f,m,s),Common::EN_ANY,Common::kPlatformMacintosh,ADGF_GENERIC | r,GUIO0() }
#define FANGAMEND(n,f,m,s) { "wage",n,AD_ENTRY1s(f,m,s),Common::EN_ANY,Common::kPlatformMacintosh,ADGF_GENERIC | ADGF_DEMO,GUIO0() }
#define FANGAMENDr(n,f,m,s,r) { "wage",n,AD_ENTRY1s(f,m,s),Common::EN_ANY,Common::kPlatformMacintosh,ADGF_GENERIC | ADGF_DEMO | r,GUIO0() }
#define BIGGAME(t,v,f,m,s) { t,v,AD_ENTRY1s(f,m,s),Common::EN_ANY,Common::kPlatformMacintosh,ADGF_DEFAULT,GUIO0() }
#define BIGGAMEr(t,v,f,m,s,r) { t,v,AD_ENTRY1s(f,m,s),Common::EN_ANY,Common::kPlatformMacintosh,ADGF_DEFAULT | r,GUIO0() }
#define BIGGAMED(t,v,f,m,s) { t,v,AD_ENTRY1s(f,m,s),Common::EN_ANY,Common::kPlatformMacintosh,ADGF_DEFAULT| ADGF_DEMO,GUIO0() }
static const ADGameDescription gameDescriptions[] = {
FANGAMEND("World Builder Demo World", "Demo World", "e221e6f9631f110b484f239e58137a3f", 45467),
FANGAME("Ray's World Template", "r:28be556dd85b5c0b28f5ac20793ba542", 38264),
// Use two files, so we override other included templates
{ "worldbuilder",
"World Builder",
AD_ENTRY2s("World Template","cb30c899eec946803793fca4e916adb8", 34444,
"Example Sound Lib", "ca2d25ee13020d5949da06b6a38a1101", 49505),
Common::EN_ANY,
Common::kPlatformMacintosh,
ADGF_GENERIC,
GUIO0()
},
// Use two files, so we override other included templates
{ "worldbuilder",
"World Builder v1.2",
AD_ENTRY2s("World Template","242d9d343cfc09d61096beb91419c493", 34772,
"Sound Library #1", "02f62fdb302c5f1e0a9a11edb972f4a3", 471067),
Common::EN_ANY,
Common::kPlatformMacintosh,
ADGF_GENERIC,
GUIO0()
},
// Render Tests
//FANGAME("Shape Test", "7466edab34a853707343c26eb362b8c1", 38194),
//FANGAME("Rects", "03d2f93b1cafe963a0e29232f4867cc4", 38177),
//FANGAME("Filled Rects", "835ae43bf16a8d83b84a99724eaec79d", 38194),
//FANGAME("Round Rects", "9602f7baafb13fd1c7fd00f92743227c", 38194),
//FANGAME("Filled Round Rects", "4d880677a97fd07e6a4f04a46f147084", 38194),
//FANGAME("Polygon", "c84d8065c6a4eb7dd232003954ba7cab", 38352),
//FANGAME("Filled Polygon", "2d5a3c952c6c43c0456eae9bbc52867d", 38310),
//FANGAME("Ovals", "9b5e3d5afe3b4c46e506216a5ae16e14", 38194),
//FANGAME("Filled Ovals", "6990174e0b39d85f3069f98511bb0074", 38194),
// Game series:
//
// The Axe-orcist
// Jamie the Demon Slayer
//
// Brownies Time Travels
// SparGate
// Brownies Dream
// Who Shot Brownie Dog?
// The Time Squisher
// Brownie Saves the Day
FANGAME("3rd Floor", "931aa0b6ada3aced5117ee6e6daf2fb8", 281153),
FANGAME("3rd Floor", "140883954b7cd89b0ffabde6ee0073d4", 281167), // alt version
FANGAME("3rd Floor", "afe1a9eb0d4e5857f9f748570862c88e", 281213), // v1.3
BIGGAME("afm", "v1.3", "Another Fine Mess 1.3", "25934edf84e814f41f3ddf87185b554b", 1409792),
BIGGAME("afm", "v1.4", "Another Fine Mess 1.4", "28d2b78c7abcbfd526f9c252444ef0b3", 1420563),
BIGGAME("afm", "v1.4", "Another Fine Mess 1.4", "r:49de322edff7118ec14b240047cef6d2", 1410160),
BIGGAME("afm", "v1.5", "Another Fine Mess 1.5", "r:9a3351c5e216d56529779a0685dba182", 1420717),
BIGGAME("afm", "v1.6", "Another Fine Mess 1.6", "r:b87fbc493f93a2b1da340b3448b18b26", 1420717),
BIGGAME("afm", "v1.8", "Another Fine Mess 1.8", "8bbec64ffe9deee4ff48d27f01176814", 1420467),
FANGAMEN("Alien Clutch", "Alien Clutch 2.0", "5c783f6b7f4a7d8d49e138be94e00f5a", 514976),
BIGGAME("amot", "v1.8", "A Mess O' Trouble 1.8", "57de8f1f79a24fa1a296aa10242c3648", 1842848),
FANGAMEN("The Adventures of Steve Reeve", "TAoSR", "e8b8ab3a838382125594aae17d53a4e7", 843323),
FANGAMEND("The Ashland Revolution", "Ashland 1.0", "f6ff38b24953ae818a000f88998e938d", 150220),
FANGAMEND("The Ashland Revolution", "xn--The Ashland Revolution Demo-uc9p", "18f1f5d1081b9a5676ccfb0c3b857059", 144767), // Original file name "The Ashland Revolution Demo†"
FANGAMEND("The Ashland Revolution", "Ashland Demo", "7a340e5ea03f7468717de408fb5b13fb", 149828), // alt version
FANGAMEND("The Ashland Revolution", "The Ashland Revolution Demo", "de6fdfe2b9d9efc0ea842e2bde68673a", 144753), // alt version
FANGAME("The Axe-orcist", "9718b72e86a9727bbd68e12fa992b268", 308508),
FANGAME("Ben's Castle 1.5", "r:6e8d74405be3875fbb85cfe8c358e641", 187691),
FANGAMEN("Beyond Twilight Vale", "Beyond Twilight Vale 0.8", "ba67d80dc22d32f42446c891daf56fc8", 248089),
FANGAME("Brownie Saves the Day", "964d9cf87422c09c50fe294adf9a7571", 680145),
FANGAME("Brownie's Dream", "379f2c810efc18bad932049d331df1b6", 440448),
FANGAME("Brownie's Dream", "78f79114a7e85e452a7b8c7346a21456", 440448), // alt version?
FANGAMEN("Brownie's Time Travels", "Brownie's Time Travels v1.2", "d95999aff4e283bd21db686cfb2cb9b9", 471333),
FANGAMEN("Brownie's Time Travels", "Brownie's Time Travels v1.2", "9dc2c6ba42788dbe534eea3ce6da91d8", 471333), // alt version
FANGAME("Bug Hunt", "b55b23a5f38d28407bd6c178a64ab629", 195443),
FANGAME("Bug Hunt", "71ffb17554e6331dd8fa63698a196bd1", 195443), // alt version
FANGAME("Bug Hunt", "cd7e1064813b9b0e3cd946e569109b34", 195523), // alt version
FANGAME("Bug Hunt", "b0fbc1b0dff61185f7bd94bf6ae7c1c8", 195509), // alt version
BIGGAME("cantitoe", "", "Camp Cantitoe", "4a44860fdfe5b9e98bcf8433801cd925", 616729),
BIGGAME("cantitoe", "", "Camp Cantitoe", "6bf7e2c13eb444a861cc07f352b6ce12", 616715), // alt version
FANGAMEN("Can James Be Found?", "xn--Can James Be Found 1.1.1-iba62n", "27822449e7e725d898181adf6e3b1689", 767445), // original file name "Can James Be Found? 1.1.1"
FANGAME("Canal District", "061a09e9258145f6a585e869a0319004", 641214),
FANGAMEN("Carbon Copy The Game", "Carbon Copy", "322410318c60045928a10f6b4c0b0a87", 519189),
FANGAMEN("Carbon Copy The Game", "Carbon Copy", "53417dce5931dfb96a21583dbafe1f8c", 331759), // alt version
FANGAMEN("Carbon Copy The Game", "Carbon Copy", "dd1f8eb1fe741cfb0df8827ecb04e584", 518580), // alt version
FANGAME("Castle of Ert", "31a832c2be51e622fb2c586803dadf9e", 198699), // alt version
FANGAME("Castle of Ert", "289d1f944d7c30b257587a7a49579d0f", 198713), // alt version
FANGAMEN("Castle of Ert", "Castle of Ert V1.2", "bc1c119b4a95ea6891a6e22c35e6c2d8", 201984),
FANGAMEN("City Adventure", "City Adventure 1.1", "0c9415ea525ffeedb346f7a62ebe6cd3", 285789),
FANGAME("Crystal Adventure", "d9e82fc3a58217a0ea4d6fddcb7fbc2a", 477918),
FANGAMEN("Crystal Search", "Crystal Search 1.5", "a9dda0422c6424e915363745ad2ecc41", 517019),
FANGAMEN("Dash Hansen and the Search for the Black Orchid", "Dash Hansen", "555e07344506720c8e4a29e1b5ba3578", 257129),
FANGAMEND("Death Mall", "Death Mall Demo", "8284351df1c7b678279139ed486ab127", 254618),
FANGAMEND("Death Mall", "Death Mall Demo", "f741a9b6ab34402fa74e42f5a4e479dd", 254018), // alt version
FANGAMEND("Death Mall", "Death Mall Demo", "39f32a73fecb19db12ba55d09c674cc9", 254604), // alt version
FANGAMEN("Deep Angst", "xn--Deep Angst-398e", "ddc4c8b3d317e2c79a12c53275253ac3", 329294), // Original file name "Deep Angst™", v.90b
FANGAMEN("Deep Angst", "xn--Deep Angst-398e", "9e4465b5547e3a5bf343e6185e600cff", 329266), // Original file name "Deep Angst™", v.90B
FANGAMEN("Deep Angst", "xn--Deep Angst-398e", "d8f782f4297b78e72e21f39b5432b0ae", 329266), // Original file name "Deep Angst™", v.90B
FANGAMEN("Deep Angst", "xn--DEEP ANGST 1987Ronald Wartow-k8a2050w", "38cc0c9a301e63b0a942677a0d19b0ac", 337122), // original file name "DEEP ANGST™ ©1987Ronald Wartow", v.90B
FANGAMEN("Deep Angst II: The Nightmare Ends", "Deep Angst II", "2e468021416bc2b82e283030c7f6bc5d", 408761),
FANGAME("Deep Ennui", "9879bf659f11c9177d578a347da0c658", 85819),
FANGAME("Deep Ennui", "4d98fcefc3e456be95023b0f06cae320", 85233), // alt version
FANGAME("Doomsday Machine", "bdb8845dfe0b4a5eb5c88da5c1b87cd6", 126371),
FANGAME("Double Trouble", "1cd05d66163b44f9f0d8c3e7232bc2db", 542115),
BIGGAME("drakmythcastle", "Disk I", "Drakmyth Castle disk I of II", "54dd0a817b667fc05c4f2dee6abe126a", 793528),
BIGGAME("drakmythcastle", "Disk I", "Drakmyth Castle", "b796545fc4b7c2b89683db873e005e92", 792355), // alt version
BIGGAME("drakmythcastle", "Disk II", "Drakmyth Castle II", "b57af17c805775d7d68b62133164c4e4", 1685403),
FANGAMENr("Dune Eternity", "xn--DUNE ETERNITY -paaanaa75fbbobb", "4946bc99cc42bf83b628352aa9b81a7b", 289945, GF_RES800), // Original file name is "***DUNE ETERNITY*** "
FANGAMENr("Dune Eternity", "xn--DUNE ETERNITY1-paaaoaa65fbbpbb", "r:4946bc99cc42bf83b628352aa9b81a7b", 289945, GF_RES800), // Same as above with different name
FANGAMEN("Dungeon World II", "DungeonWorld2", "74a7153f9ae61a59a216078a37f68f2c", 229943),
FANGAMEr("Dynasty of Dar", "e118a261d33831c224f3b776ec5dd2a8", 275437, GF_RES800),
FANGAMEr("Dynasty of Dar", "e15eae8d9a4c40512be3bb81ecedf32c", 275423, GF_RES800), // alt version
FANGAME("Edg's World", "480bcf68be49ee3765902e922ccdc833", 106513),
FANGAME("Edg's World", "bcbfbf4d2baeadc345f62246279ce3d6", 105927), // alt version
FANGAME("Eidisi I", "ed8fec61ad94ddec06feaf4eb720084b", 172296),
FANGAME("Eidisi I", "a20f080d6109d8e253d74b0dfb4b2ae5", 172296), // alt version
FANGAME("Eidisi I", "06ae31c4361f9bd5b91593858b6d0d79", 172310), // alt version
FANGAME("Elite Commando", "3fc74f1403c1529b52f3cd4d60771dac", 293704), // v1.0
FANGAMEN("Enchanted Pencils", "Enchanted Pencils 0.99 (PG)", "49a0708da81dbeb28c6e607429c92209", 408657),
FANGAMEN("Escape!", "xn--Escape!-z46c", "28a9658ee846a34f133df29b54cf255a", 64819), // Original file name "Escape!†"
FANGAMEN("Escape!", "xn--Escape!-lha", "63378d6b032d4337b7e74759337eceaa", 64805), // Original file name " Escape!", alt version
FANGAMEN("Escape from Inaka", "Escape from Inaka - 1.0b1", "a1a91e9b7b6ad01d779993dab24fd080", 1112974),
FANGAME("Escape from School!", "2055747bb874052333190eb993246a7f", 49849),
FANGAMEN("Escape from School!", "xn--Escape from School!-3g6k", "fcc581e52d1fc8ea4603d7c953fa935a", 49863), // Original file name "Escape from School!†", alt version
FANGAME("Escape From Saecvrom", "ab401f96ca0755bf5fb849f71cc47d83", 588982),
FANGAME("Everyman 1", "97d78e22493245636f84ad8db732305c", 335449),
FANGAME("EveryMan 1", "9104b23c8978c7db118bb60fb5f8bf0f", 335435), // alt version
FANGAMEN("EveryMan 1", "EveryMan I", "9104b23c8978c7db118bb60fb5f8bf0f", 335435), // Same as above with different name
FANGAME("EveryMan 1", "829aabea15e143f20fe692f0bc38e56a", 335444), // alt version
FANGAMEN("EveryMan 2", "EveryMan 2 2.02", "401c1b0eae618fa5f39b6577bc108c2f", 715273),
FANGAME("Exploration Zeta!", "9006eff549afadc956e5de4ae6a24fbd", 366343),
FANGAMEND("Explorer", "Explorer DEMO", "0ae79f48754466c4cd65137c7f186384", 460972),
FANGAMEND("Explorer", "Explorer DEMO", "6b22972808630201ece7cc96a0ef17f7", 460959), // alt version
FANGAME("Fantasy Quest", "b52d3e2680a76c23b2791e2c87f6b6bd", 762498),
FANGAMEND("Finding D", "Finding D - concept demo", "3657a0483c70a1b44641e62120290574", 55723),
FANGAME("Find the Heart", "0c0c282649597ea1ac82d97c8d4029a2", 105979), // From Joshua's Worlds 1.0
FANGAME("Find the Heart", "6fb8c6b8bc2287ba225b0ac6580dc073", 105675), // From Joshua's Worlds 1.0, alt version
FANGAME("Find the Heart", "08de3248b8c691d9a08af984bdcfa872", 105629), // From Joshua's Worlds, alt version
FANGAME("Find the Heart", "73935d313b666763e50d2cdc6b3b7908", 105615), // Standalone
FANGAMEN("Fortune Teller", "Fortune Teller 1.1", "3c09329fc3c92a70e5c8874cc763f5cb", 73675),
FANGAMEN("Fred Rogers - Terrorist", "xn--Terrorist-od0e", "4398f711bc2a456e6d730641308593f0", 524213), // Original file name "Terrorist†"
FANGAMEN("Fred Rogers - Terrorist", "Terrorist", "a37f39fcf9a1c88e1b30385754b2a1f7", 524199), // alt version
FANGAME("Fred Rogers - Terrorist", "8597a77619847efbce3f1f8b2ceb3258", 524199), // alt version
FANGAME("Galactic Warrior", "bf61d2344a63b597ab288ddaacfa402a", 89117),
FANGAMEN("get-a-mac", "get-a-mac 1.1", "e12a38d9b56ca62414b99cdb58aa3e1c", 144936),
FANGAMEN("Get out of MAZE", "Get out of MAZE 1.0", "cd9eb31e1178b0eec35ee84ff781647b", 53583),
FANGAME("Gold Bug!", "34a379231ae97150e336b91773e6dd47", 782645),
BIGGAME("grailquest", "", "GrailQuest", "6441d134c0aedd8cb3b6909107a22ee3", 739452),
BIGGAMED("grailquest", "Demo", "GrailQuest.demo", "c0ef30ef7e4119fe76c5fbe2f930e7b1", 190810),
FANGAME("Haikook", "405d383b273a0252645962e0e664be20", 419636),
FANGAME("Grendel-Dazz", "968addd863d8e5c90f73d029c9240ded", 267156),
FANGAMEN("Haunted House", "The haunted house 1.4", "655ff082160909beb83b99979cdfc2c6", 107959),
FANGAMEN("Haunted House", "Haunted House 1.5", "5e34e9fa13f4c90876f10a40ea1d1c79", 177244),
FANGAMEN("Haunted University", "Haunted U. v2.04", "d9511905bb908a0c3ea13c996e053aec", 1674435),
FANGAME("Hollywood Starbound", "34499b3935f4c019c05e77ade28aa527", 633717),
FANGAMEN("The Hotel Caper", "The Hotel Caper V1.0", "c9b3c75814fc6b14feae044157bef252", 231713),
FANGAMEN("The Hotel Caper", "The Hotel Caper V1.0", "4658ece81a6f211a828e747125482f48", 231713), // alt version
FANGAMEN("The Hotel Caper", "The Hotel Caper V1.0", "baaaae4569fbb1947d74667ac484bea9", 231773), // alt version
FANGAMEN("The Hotel Caper", "Hotel Caper V1.0", "138e89e5d5bcac6aba222cf33677bcec", 231127),
FANGAMEN("The Hotel Caper", "The Hotel Caper V2.0", "eed79653ca95f4e953aa0ff41e8c8034", 260418),
FANGAMEN(".i.f.", "xn--.i.f.-", "575fc0c25e9823dd742de1acdcaac325", 183221),
FANGAMEND("Introduction to Gothic", "Intro to Gothic", "606eec666f0b2d767e4423747e740434", 207811),
FANGAMEN("Intruder", "INTRUDER 1.1", "7d819891784c79fef66ff3bd9d946d6d", 203105),
FANGAMEN("Intruder", "INTRUDER v1.2", "a7df3f4196e922153cd4345def774c29", 241007),
FANGAME("Intruder ][", "r:a35a5e110bb5e4a3a3b91db26deaa881", 697570),
FANGAME("Jack & Beanstalk Starring You", "2cd38be71d32892a83913c061a03aa19", 312646),
FANGAMEN("James Bond 007", "007", "2449924f2cb43454489a4ef91c0ee702", 50407),
FANGAME("Jamie the Demon Slayer", "ed054aa760569059c7ea554e822089a6", 232533),
FANGAME("Joe Wu Tse's Dungeon", "r:ea800af93f3886550d7c6c1687cfc9e1", 189764),
FANGAMEN("Journey", "The Journey 1.5", "4bbc047d21740f79f3f198628ba8f8bc", 665011),
FANGAMEN("Journey", "2 The Journey v1.5 (1992)", "4bbc047d21740f79f3f198628ba8f8bc", 665011), // alt version
FANGAMEN("Journey", "1 The Journey v1.6 (1992)", "cff7929abf284060c2070dfc1a2fa9cf", 765189),
FANGAMEN("Journey", "The Journey 1.6.2 US", "588a516caa187005fdfcbc72823c8eff", 820316),
FANGAMEN("Jumble", "xn--LSJUMBLE -nd0e", "555ead186ec1683157e53b96fc4a99d5", 647083), // Original file name is "LSJUMBLE† "
FANGAME("Karth of the Jungle", "6dae6eef6f46101ba8c9e312bb82e824", 96455),
FANGAME("Karth of the Jungle", "52414de10b5d0f75a582740d8d3face6", 96455), // alt version
FANGAME("Karth of the Jungle", "c869cacc59f2402d06bc8197be28b5df", 96704), // alt version
FANGAME("Karth of the Jungle", "b1f4fbf94d685f22204e4c7d81edd0b4", 95869), // alt version
FANGAME("Karth of the Jungle II", "6f26e0bace5c41d91c135e5e102de5bc", 200797),
FANGAMEN("Karth of the Jungle II", "Karth II", "dd4d719aabc78f82123721d3bd6c28fb", 200419),
FANGAMEN("Labyrinth of Destiny", "Labyrinth of Destiny 1.0.5", "74f65f537358137721920e16d684f00b", 461053),
FANGAMEN("Little Pythagoras", "Little Pythagoras 1.1.1", "66a5d2ac1a0df2ee84cbee583c1f1dfe", 628565),
FANGAME("Lost Crystal", "945a1cf1ead6dd298305935b5ccb21d2", 770816),
FANGAMEN("Lost In Kookyville", "Lost In Kookyville 1.2.4", "89ecb4cef5cc4036e54f8bc45ce7520e", 721313),
FANGAMEN("Lost In Kookyville", "Lost In Kookyville 1.2.4", "dfa0abbadf3848f415c6696d6d914455", 722917), // alt version?
FANGAME("Lost Princess", "29d9889b566286506ded7d8bec7b75ce", 166502),
FANGAME("Lost Weekend", "5952ce2396c162e478f49b93f64dbefc", 178443),
FANGAME("Lost Weekend", "c1f3de4fedf59b0a7637bbb2a0ed16d6", 178443), // 24-bit version
FANGAME("The Lost Skater", "5d1fa9ac715ce780ace9aa018caadb51", 334718),
{ "wage",
"Mac Spudd!",
AD_ENTRY2s("Mac Spudd!","eaba9195dd27c2a26b809a730417122b",781859,
"Mac Spudd! Sounds", "4c57693915fbe9ce6c72a40d84c4e929", 663598),
Common::EN_ANY,
Common::kPlatformMacintosh,
ADGF_GENERIC,
GUIO0()
},
{ "wage",
"Mac Spudd!",
AD_ENTRY2s("Mac Spudd!","eaba9195dd27c2a26b809a730417122b",781859,
"Mac Spudd! Sounds", "75fb4df3390de44be771bf6cb491b085", 663716),
Common::EN_ANY,
Common::kPlatformMacintosh,
ADGF_GENERIC,
GUIO0()
},
FANGAME("MacWanker 1.0", "2fd407020adf527d1193f3351d7244b5", 178005),
FANGAME("Magic Rings", "263e2c90af61f0798bf41f6a1e3f6345", 108788),
FANGAMEN("Mansion!", "Mansion! 1.0", "c993619e5bd99ccca6e3ce28d93be33a", 389857),
FANGAMEN("Maze of the Questing Beast", "MQB", "03775e1894809f24234aeaab18f39451", 134444),
FANGAMEN("Maze of the Questing Beast", "MQB", "e2b89bd1ae34114445a4bb4795f0f514", 134444), // alt version
FANGAMEN("Maze of the Questing Beast Solutions", "MQB Solutions", "8a27e515f12162cc8b85e4f2bd16a1e5", 37606),
FANGAME("Messy House", "32ca71f2ff37997407cead590c2dd306", 176864),
FANGAME("Midnight Snack", "70ba8a5a1f0304669c9987360bba236f", 67696),
FANGAME("Midnight Snack", "24973af10822979e23866d88a7d2e15c", 67710), // alt version
FANGAME("Midnight Snack", "0179e0fbc7370f31e4b64f40d2b44f71", 67110), // alt version
FANGAME("Mike's House", "591b5a4d52ecf9a7d87256e83b78b0fd", 87101),
FANGAME("Mike's House", "e4c0b836a21799db3995a921a447c28e", 87087), // alt version
FANGAME("Mike's House", "79c5e9ffaacfd8aadddc7c2923655a41", 86775), // alt version
FANGAME("Mindy - The Quest for Home", "a60f17662adf23146e5247d4d1e46d07", 117183),
FANGAME("Minitorian", "c728dfccdca12571e275a4113b3db343", 586208),
FANGAMEN("M'Lord's Warrior", "xn--M'Lord's Warrior -f82j", "e0a0622ce2023984e3118141a9688ec5", 465383), // Original file name is "M'Lord's Warrior †"
FANGAMEN("Mormonoids from the Deep", "Mormonoids 1.0", "15c99d26ac2abb9eb4802a7dd5326086", 642975),
FANGAMEN("Mormonoids from the Deep", "Mormonoids 1.25", "4730d0c47d13401d73353e980f91a304", 645062),
FANGAMEN("Mormonoids from the Deep", "Mormonoids 1.25", "1a7ee052b375f0c0a4c18836c978ce5b", 645077), // alt version
FANGAMEN("Mountain of Mayhem", "xn--Mountain of Mayhem -3g6k", "634211b004371635d191ae0687035501", 749747), // Original file name "Mountain of Mayhem †"
FANGAME("Mountain of Mayhem", "c83a5703b3ea95f465839f4f54ef0805", 749733), // alt version
FANGAME("Mystery of the Moors", "593dc2fcb92c0f5f4d94b5fc390a2ba2", 849909),
FANGAME("Necropolis", "70fbba8a0b1626216f674df2ca9c58ba", 397396),
FANGAME("Nightcrawler Ned", "8423fc015c395bd6be54a7ea69170d99", 366286),
FANGAMEND("Odyssey", "Odyssey Demo", "c5b23c29bada41b587a86c7090570578", 256842),
FANGAMEN("Parrot Talk", "PARROT TALK V1", "c38c090be8c078d931905c93bc0689f5", 118680),
FANGAMEN("Parrot Talk", "PARROT TALKV2", "5ec1df9e2d6d0dcf1a040a95500d9551", 118628),
FANGAME("Pavilion", "a980e60a291c0a7b949474177affa134", 231431),
FANGAMEN("Pencils", "Pencils.99", "09dbcdbefe20536c2db1b1a4fb4e5ed3", 408295),
FANGAMEN("Pencils", "Pencils.99", "9757cc28d82cea636ee260b9aa33c167", 407695), // alt version
FANGAMEN("Pencils", "Pencils.99", "3ae42a43cd51aaf624ecfeea7d00d401", 408281), // alt version
FANGAME("Periapt", "7e26a7827c694232624321a5a6844511", 405750),
FANGAME("Periapt", "bc36e40de279d5f0844577fe702d9f64", 405750), // alt version
FANGAME("Periapt", "661642865321fa81ce84ae2eedbc1aff", 405736), // alt version
FANGAMEN("Periapt", "Periapt.1", "661642865321fa81ce84ae2eedbc1aff", 405736), // alt version
FANGAME("The Phoenix", "bd6dabf7a19d2ab7902498a8513f8c71", 431387),
FANGAMEN("The Phoenix v1.2", "The Phoenix", "fee9f1de7ad9096d084461d6066192b1", 431384),
FANGAME("Pirate Attack!", "d4d3f59b57d5bf3dd690fd657ecdd9c6", 323722),
FANGAME("Porno Writer", "9d521bf40f6824228266923774aadfea", 467342),
FANGAME("Porno Writer 2.0", "b5228c22158ff40ccac36b6a61cc317a", 791379),
FANGAME("Pretty Good Pornography 3.0", "c86c92ec69b464a197659b1bd80880b9", 1122264),
FANGAME("Psychotic!", "d6229615b71b189f6ef71399a0856cd2", 367053),
FANGAME("Psychotic!", "c7608f67592563b44f2f48fe5fec63ce", 367067), // alt version
FANGAME("Psychotic!", "51aa5f2744ceb5666c9556bccee797d6", 367173), // another alt version
FANGAME("Puzzle Piece Search", "6c21c1e0c6afef9300941abd7782dd16", 247437), // From Joshua's Worlds 1.0
FANGAME("The Puzzle Piece Search", "8fa1d80dd3f1ed69f45d15d774968995", 247082), // From Joshua's Worlds
FANGAME("The Puzzle Piece Search", "bb4d42fd5936ac7fec92a8efc1987653", 247128), // From Joshua's Worlds¸ alt version
FANGAME("The Puzzle Piece Search", "fb839ac4f22427f44e99bcc5afd57a0b", 247068), // Standalone
FANGAME("Pyramid of No Return", "4bf4c39b140f5aadb5f8c9a50153d18e", 384889),
FANGAME("P-W Adventure", "a8e9f97ee02f01de588a4dbabe55ca3f", 218960),
FANGAMEN("Pyramid of Ert", "Pyramid of Ert V1.2", "358b03ea9c978fbfd2ce2833daea00f8", 315527),
FANGAME("Queen Quest", "7ca009dad76827ce008c3c7fa01cab0a", 56770),
FANGAME("Queen Quest", "cde1255992101e0763687f45e0f47169", 56783), // alt version
FANGAME("Quest for T-Rex", "51aa29d24ca702c8980e959706ef0b76", 592328),
FANGAME("Quest for T-Rex", "f1edd78817e5a72fb7d85879f168fc8c", 592328), // alt version
FANGAME("Quest for T-Rex", "31ef93ae4ee7b48e3720b977dcf480c3", 592314), // alt version
FANGAME("Quest for T-Rex", "90d5ac734a3944480bdeb4a89524db41", 591728), // alt version
FANGAMEN("Quest for T-Rex", "3 Quest for T-Rex (19901125)", "1ff85bc2ca98e016bc4809ba4499b58b", 595989), // alt version
FANGAMEN("Quest for T-Rex", "Quest for T-Rex 1.2", "edd1164eccad3eaa23b3636000b5a047", 593924),
FANGAMEN("Quest for the Dark Sword", "xn--Quest for the Dark Sword-3r4o", "d98c3879ff20ca7e835e3a630c2c74ef", 572320), // original file name "Quest for the Dark Sword™"
FANGAMEN("Quester: The Forbidden Lands", "Quester (Demo)", "r:5f0b06526d3a951121333953f559da3f", 254868),
FANGAMEN("Quester: The Forbidden Lands", "Quester-The Forbidden Lands", "5f0b06526d3a951121333953f559da3f", 254868),
FANGAME("Radical Castle", "e424dea5542817435b4b6da41fd532cb", 355345),
FANGAME("Radical Castle", "c8393e30ecf376b9635e30894eacecd0", 347022), // alt version
FANGAME("Radical Castle", "be56ff729b11793dc3da76336ec1abe0", 358533), // alt version
FANGAMEN("Radical Castle", "Radical Castle 1.0", "7c7701dcab013c65510f97a8712fc419", 347022),
FANGAMEN("Radical Castle", "Radical Castle 2.0", "ff1e2292dc49622b53dc04f6fa2e3577", 357418),
BIGGAME("raysmaze", "v1.5", "Ray's Maze1.5", "328096d3c7ccb838956c42b273f17b8e", 1408260),
BIGGAME("raysmaze", "v1.5/alt", "Ray's Maze1.5", "401cd46df5e49fa4171ed398b3e0227b", 1408260),
FANGAMEN("Realms & Reality", "Realms & Reality 1.2", "13ee1c40855bdf666edafcf554ec35d6", 492358),
FANGAME("Ray's World Builder Demo", "eafea10db54530ac6e6128be31741542", 115800),
FANGAME("Robot Planet", "1066f6b2892cda16c2f365d1ec97537c", 107089),
FANGAME("Royal Malaise, Part 1", "a12d6990b2a0d1ce3cca5f19c8ef70cc", 306481),
FANGAMEN("Sands of Time", "xn--Sands of Time-1s6g", "d065662865d0cb9065812479ed7d2795", 122416), // Original file name "Sands of Time†"
FANGAMEN("Sands of Time", "Sands of Time", "466add24c5092937afca005041c7150b", 122380), // alt version
BIGGAME("scepters", "", "Scepters", "ecb776fb660205fad5819a26f7e180b5", 347103), // original 1986 version
BIGGAME("scepters", "", "Scepters", "e6c58e96b02f8eb46e0ccfe4f547045b", 346339), // alt version
BIGGAME("scepters", "", "Scepters", "2c824f1bd7b22c575c7be86ac88ebd23", 347284), // alt version
FANGAME("Schmoozer", "7bbb3d317d2074870c72b28d07984ef8", 221244),
FANGAME("Schmoozer", "db31e320f286a5f4ab2c70022d24dcb2", 221244), // alt version
FANGAME("sMythWorld", "8224b15a910c9e7515923107d138de77", 472101),
FANGAME("Sorceror's Quest (Demo)", "r:ee8efbccea0807674af2bb53e6edf338", 531622),
FANGAMEND("Space Adventure", "SpaceAdventure", "3908c75d639989a28993c59931fbe1ec", 155100),
FANGAMEND("Space Adventure", "SpaceAdventure", "e38d524cb778ed0beb77ee9299f0ed45", 155100), // alt version
FANGAMEND("Space Adventure", "SpaceAdventure", "6cc726d460c76fbe8a4d16ce1831d4e7", 155086), // alt version
FANGAME("Space Adventure", "6cc726d460c76fbe8a4d16ce1831d4e7", 155086), // identical to the above, different file name
FANGAME("SpaceAdventure (Demo)", "r:6cc726d460c76fbe8a4d16ce1831d4e7", 155086), // identical to the above, different file name
FANGAMEN("SparGate", "SparGate- vNC.1", "a7a7bfc1825011c2df8b7a03b57fcac9", 611991),
FANGAMEN("Spear of Destiny", "xn--SpearOfDestiny-ef3h", "ac00a26f04f83b47c278cc1d226f48df", 333409), // Original file name "SpearOfDestiny†"
FANGAMEN("Spear of Destiny", "SpearOfDestiny", "64faf0d0324e25dbe5519a8bbdb43081", 332809), // alt version
FANGAMEN("Spear of Destiny", "Spear Of Destiny", "93fa152f3de31c0202e978fe2e9d78f7", 333395), // alt version, different file name
FANGAME("Spear of Destiny", "ea90bddd0925742351340cf88dd1c7a6", 620350), // alt version, different file name
FANGAME("Star Trek", "3067332e6f0bb0314579f9bf102e1b56", 53064),
FANGAME("Star Trek", "ad91a24fa79a64fd11fff84740073659", 47280), // alt version
FANGAME("Star Trip", "3067332e6f0bb0314579f9bf102e1b56", 53064),
FANGAME("Starport", "d9e25edc9ea5d2a8b29a3693de19ae2c", 253970),
FANGAMEN("Strange Disappearance", "xn--Strange Disappearance -", "9d6e41b61c0fc90400e5da2fcb653a4a", 772026), // Original file name "Strange Disappearance "
FANGAME("The Sultan's Palace", "fde31cbcc77b66969b4cfcd43075341e", 456599), // Releases titled "Palace of Sand" were made with FutureBasic
FANGAMEN("Success", "Success 1.0", "bacb07694a4559ae617923484aa40505", 93935),
FANGAMEN("Swamp Witch", "xn--Swamp Witch-dl3f", "bd8c8394be31f7845d55785b7ccfbbde", 739525), // Original file name "Swamp Witch†"
FANGAME("Swamp Witch", "07463c8b3b908b0c493a41b949ac1ff5", 739875), // alt version
FANGAME("Swamp Witch", "865a07a0356926d6cab8f14208e11f9c", 739511), // alt version
FANGAME("Swamp Witch", "dcda6bb0f27ae66884cab75ffc04e0d9", 739525), // alt version
FANGAME("Sweetspace Now!", "574dc7dd25543f7a516d6524f0c5ab33", 123557), // Comes with Jumble
FANGAMEN("Sword of Ages", "Sword of Ages 2.1", "d6dfea87c29dfe32dd958bbba25c70f3", 218673),
FANGAMEN("Sword of Siegfried", "Sword of Siegfried 1.0", "2ae8f21cfb228ce58ee47b767bdd8820", 234507),
FANGAMEN("Sword of Siegfried", "Sword of Siegfried 1.0", "7a68b00019592f3aa671cae024f7e843", 234493), // alt version
FANGAME("Sword of Siegfried", "7a68b00019592f3aa671cae024f7e843", 234493), // identical to the above, different file name
FANGAME("Time Bomb", "e96f3e2efd1e3db6ad0be22180f5473c", 64308),
FANGAME("Time Bomb", "976180f9be0d1029aaba7774fec9767c", 64322), // alt version
FANGAMEN("The Time Squisher", "Time Squisher v 1.1", "3e296141e7a7c9b05e0dde779d9bb50d", 463526),
FANGAMEND("Time Star", "xn--Time Star-eh2e", "761e91f3da7217dc9516879a8ba08fd9", 210562), // Original file name "Time Star™"
FANGAMEND("Time Streak!!", "xn--Time Streak!!-8q9g", "e90f123a113e38c83c205b5fe50521fb", 483488), // Original file name "Time Streak!!™"
FANGAME("The Tower", "4cd8755ccb5bbeaf2e5f7848a8daa033", 556283),
FANGAME("The Tower", "r:a79282051dd190bb8d41c7600bdc157e", 733539), // alt version
FANGAMEND("Tombworld", "xn--Demo TombWorld-8ra", "695734292024290d5d0aa6a66ff628f6", 663996), // Original file name "Demo TombWorld©"
FANGAMEND("Tombworld", "DemoTombWorld", "4b3f56cfa595eab3919a918767e11870", 387403), // alt version
BIGGAME("twisted", "", "Twisted! 1.1", "7226874582d7e5fa13004340e9c5ba2b", 957878),
BIGGAME("twisted", "", "Twisted! 1.5", "786909ab45da5af2b96f3a96a63727f0", 960186),
BIGGAME("twisted", "", "Twisted! 1.6", "6e0be4c7d83231e56a431dc4ef7bf978", 960698),
FANGAME("The Village", "fd35cad8c61064d6c8aaadab7070ccad", 314572),
FANGAME("The Village", "b84ac84be610498c4de5d9bd4fd36175", 314620), // alt version
FANGAMEN("Volcano I", "Volcano", "r:d55531ddaa800b77e8f3da6385283183", 101635),
FANGAMEN("Volcano", "Volcano 2.4", "d04eba23fe7d566fba0c9b34242d8f10", 227431),
FANGAMEN("Volcano II", "xn--Volcano II-0y6e", "7941c08b34bc2408b98c0004c7154aeb", 82735), // Original file name "Volcano II†"
FANGAMEN("Who Shot Brownie Dog?", "xn--Who Shot Brownie Dogv1.01-lba88n", "4e7a950567c73e98e4478e5683e943ca", 195923),
FANGAME("Widow's Sons", "r:1bfae1966acb268c947a4ba69e0f0693", 65796),
FANGAMEN("Wishing Well", "xn--Wishing Well -", "607a66461d65a73c1222d63dbde0175d", 103432), // original file name "Wishing Well "
FANGAMEN("Wishing Well", "xn--Wishing Well -", "5fcd9ac410bffd6369fb8723e86e6420", 103421), // original file name "Wishing Well ", alt version
FANGAME("The Wizard's Apprentice", "477c2d2d4672d968dfac1df2acef1dc9", 782568),
FANGAME("Wizard's Warehouse", "18b4678c76961c1f3ae3013e13f691a3", 159492),
FANGAME("Wizard's Warehouse", "d0ab2d4b0009eb61cbff925f8c382379", 158846), // alt version
FANGAMEN("Wizard's Warehouse 2", "WizWarehouse 2.0", "e67ac67548236a73897a85cd12b30492", 230614),
FANGAMEN("Wizard's Warehouse 2", "WizWarehouse 2.0", "418cc7a44477a4408ea365c8de86f577", 230614), // alt version
FANGAME("Zhore's Xers 2.3", "r:a4d6748fc48988bd6d216468a56b14d6", 484867),
FANGAME("ZikTuria", "a91559e6f04a67dcb9023a98a0faed77", 52716),
FANGAMEN("Zoony", "xn--Zoony-j64b", "3f7418ea8672ea558521c178188cfce5", 154734), // original file name "Zoony™"
FANGAMEN("Zoony", "xn--Zoony-j64b", "55d3d42b5dca9630eb28ad464f343c67", 154734), // original file name "Zoony™", alt version
FANGAMEN("Zoony", "xn--Zoony-j64b", "ecbe19c53f9d5b7ef62e66db9ea42348", 154970), // original file name "Zoony™", alt version
FANGAMEN("Zoony", "xn--Zoony-j64b", "d75c2fb1a6ddd15f7f6e25b721fb6bfb", 154853), // original file name "Zoony™", alt version
FANGAMEN("Zoony", "xn--Zoony-j64b", "f0741c2378f03bc17969078658a5a6ca", 154148), // original file name "Zoony™", alt version
AD_TABLE_END_MARKER
};
} // End of namespace Wage

View File

@@ -0,0 +1,52 @@
/* 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 WAGE_DT_INTERNAL_H
#define WAGE_DT_INTERNAL_H
namespace Wage {
typedef struct ImGuiImage {
ImTextureID id;
int width;
int height;
} ImGuiImage;
typedef struct ImGuiState {
bool _showWorld = false;
Common::HashMap<Common::String, ImGuiImage> _images;
Common::Array<ImGuiImage> _patternTextures;
ImGuiTextFilter _nameFilter;
int _currentStep = 1;
bool _showScriptWindow = false;
int _selectedScene = 0;
int _selectedObj = 0;
int _selectedChr = 0;
int _selectedSound = 0;
} ImGuiState;
extern ImGuiState *_state;
}
#endif // WAGE_DT_INTERNAL_H

495
engines/wage/entities.cpp Normal file
View File

@@ -0,0 +1,495 @@
/* 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/>.
*
* MIT License:
*
* Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#include "wage/wage.h"
#include "wage/entities.h"
#include "wage/design.h"
#include "wage/gui.h"
#include "wage/script.h"
#include "wage/world.h"
#include "common/memstream.h"
#include "graphics/managed_surface.h"
#include "graphics/macgui/macfontmanager.h"
namespace Wage {
void Designed::setDesignBounds(Common::Rect *bounds) {
_designBounds = bounds;
}
Designed::~Designed() {
delete _design;
delete _designBounds;
}
Context::Context() {
_visits = 0;
_kills = 0;
_experience = 0;
_frozen = false;
_freezeTimer = 0;
for (int i = 0; i < 26 * 9; i++)
_userVariables[i] = 0;
for (int i = 0; i < 18; i++)
_statVariables[i] = 0;
}
Scene::Scene() {
_resourceId = 0;
_script = NULL;
_design = NULL;
_textBounds = NULL;
_font = NULL;
for (int i = 0; i < 4; i++)
_blocked[i] = false;
_soundFrequency = 0;
_soundType = 0;
_worldX = 0;
_worldY = 0;
_visited = false;
}
Scene::Scene(Common::String name, Common::SeekableReadStream *data) {
debug(9, "Creating scene: %s", name.c_str());
_name = name;
_classType = SCENE;
_design = new Design(data);
_resourceId = 0;
_script = NULL;
_textBounds = NULL;
_font = NULL;
setDesignBounds(readRect(data));
_worldY = data->readSint16BE();
_worldX = data->readSint16BE();
_blocked[NORTH] = (data->readByte() != 0);
_blocked[SOUTH] = (data->readByte() != 0);
_blocked[EAST] = (data->readByte() != 0);
_blocked[WEST] = (data->readByte() != 0);
_soundFrequency = data->readSint16BE();
_soundType = data->readByte();
data->readByte(); // unknown
_messages[NORTH] = data->readPascalString();
_messages[SOUTH] = data->readPascalString();
_messages[EAST] = data->readPascalString();
_messages[WEST] = data->readPascalString();
_soundName = data->readPascalString();
_visited = false;
delete data;
}
Scene::~Scene() {
delete _script;
delete _textBounds;
delete _font;
}
void Scene::paint(Graphics::ManagedSurface *surface, int x, int y) {
Common::Rect r(x, y, surface->w + x, surface->h + y);
surface->fillRect(r, kColorWhite);
_design->paint(surface, *((WageEngine *)g_engine)->_world->_patterns, x, y);
for (ObjList::const_iterator it = _objs.begin(); it != _objs.end(); ++it) {
debug(2, "painting Obj: %s, index: %d, type: %d", (*it)->_name.c_str(), (*it)->_index, (*it)->_type);
(*it)->_design->paint(surface, *((WageEngine *)g_engine)->_world->_patterns, x, y);
}
for (ChrList::const_iterator it = _chrs.begin(); it != _chrs.end(); ++it) {
debug(2, "painting Chr: %s", (*it)->_name.c_str());
(*it)->_design->paint(surface, *((WageEngine *)g_engine)->_world->_patterns, x, y);
}
}
Designed *Scene::lookUpEntity(int x, int y) {
for (ObjList::const_iterator it = _objs.end(); it != _objs.begin(); ) {
it--;
if ((*it)->_design->isInBounds(x, y))
return *it;
}
for (ChrList::const_iterator it = _chrs.end(); it != _chrs.begin(); ) {
it--;
if ((*it)->_design->isInBounds(x, y))
return *it;
}
return nullptr;
}
Obj::Obj() : _currentOwner(NULL), _currentScene(NULL) {
_index = 0;
_resourceId = 0;
_namePlural = false;
_value = 0;
_attackType = 0;
_numberOfUses = 0;
_returnToRandomScene = false;
_type = 0;
_accuracy = 0;
_damage = 0;
}
Obj::Obj(Common::String name, Common::SeekableReadStream *data, int resourceId) {
_resourceId = resourceId;
_name = name;
_classType = OBJ;
_currentOwner = NULL;
_currentScene = NULL;
_index = 0;
_design = new Design(data);
setDesignBounds(readRect(data));
int16 namePlural = data->readSint16BE();
if (namePlural == 256)
_namePlural = true; // TODO: other flags?
else if (namePlural == 0)
_namePlural = false;
else
error("Obj <%s> had weird namePlural set (%d)", name.c_str(), namePlural);
if (data->readSint16BE() != 0)
error("Obj <%s> had short set", name.c_str());
if (data->readByte() != 0)
error("Obj <%s> had byte set", name.c_str());
_accuracy = data->readByte();
_value = data->readByte();
_type = data->readSByte();
_damage = data->readByte();
_attackType = data->readSByte();
_numberOfUses = data->readSint16BE();
int16 returnTo = data->readSint16BE();
if (returnTo == 256) // TODO any other possibilities?
_returnToRandomScene = true;
else if (returnTo == 0)
_returnToRandomScene = false;
else
error("Obj <%s> had weird returnTo set", name.c_str());
_sceneOrOwner = data->readPascalString();
_clickMessage = data->readPascalString();
_operativeVerb = data->readPascalString();
_failureMessage = data->readPascalString();
_useMessage = data->readPascalString();
_sound = data->readPascalString();
delete data;
}
Obj::~Obj() {
}
Chr *Obj::removeFromChr() {
if (_currentOwner != NULL) {
for (int i = (int)_currentOwner->_inventory.size() - 1; i >= 0; i--)
if (_currentOwner->_inventory[i] == this)
_currentOwner->_inventory.remove_at(i);
for (int i = 0; i < Chr::NUMBER_OF_ARMOR_TYPES; i++) {
if (_currentOwner->_armor[i] == this) {
_currentOwner->_armor[i] = NULL;
}
}
}
return _currentOwner;
}
Designed *Obj::removeFromCharOrScene() {
Designed *from = removeFromChr();
if (_currentScene != NULL) {
_currentScene->_objs.remove(this);
from = _currentScene;
}
return from;
}
void Obj::resetState(Chr *owner, Scene *scene) {
removeFromCharOrScene();
warning("STUB: Obj::resetState()");
}
Chr::Chr(Common::String name, Common::SeekableReadStream *data) {
_name = name;
_classType = CHR;
_design = new Design(data);
_index = 0;
_resourceId = 0;
_currentScene = NULL;
setDesignBounds(readRect(data));
_physicalStrength = data->readByte();
_physicalHp = data->readByte();
_naturalArmor = data->readByte();
_physicalAccuracy = data->readByte();
_spiritualStength = data->readByte();
_spiritialHp = data->readByte();
_resistanceToMagic = data->readByte();
_spiritualAccuracy = data->readByte();
_runningSpeed = data->readByte();
_rejectsOffers = data->readByte();
_followsOpponent = data->readByte();
data->readSByte(); // TODO: ???
data->readSint32BE(); // TODO: ???
_weaponDamage1 = data->readByte();
_weaponDamage2 = data->readByte();
data->readSByte(); // TODO: ???
if (data->readSByte() == 1)
_playerCharacter = true;
else
_playerCharacter = false;
_maximumCarriedObjects = data->readByte();
_returnTo = data->readSByte();
_winningWeapons = data->readByte();
_winningMagic = data->readByte();
_winningRun = data->readByte();
_winningOffer = data->readByte();
_losingWeapons = data->readByte();
_losingMagic = data->readByte();
_losingRun = data->readByte();
_losingOffer = data->readByte();
_gender = data->readSByte();
if (data->readSByte() == 1)
_nameProperNoun = true;
else
_nameProperNoun = false;
_initialScene = data->readPascalString();
_nativeWeapon1 = data->readPascalString();
_operativeVerb1 = data->readPascalString();
_nativeWeapon2 = data->readPascalString();
_operativeVerb2 = data->readPascalString();
_initialComment = data->readPascalString();
_scoresHitComment = data->readPascalString();
_receivesHitComment = data->readPascalString();
_makesOfferComment = data->readPascalString();
_rejectsOfferComment = data->readPascalString();
_acceptsOfferComment = data->readPascalString();
_dyingWords = data->readPascalString();
_initialSound = data->readPascalString();
_scoresHitSound = data->readPascalString();
_receivesHitSound = data->readPascalString();
_dyingSound = data->readPascalString();
_weaponSound1 = data->readPascalString();
_weaponSound2 = data->readPascalString();
for (int i = 0; i < NUMBER_OF_ARMOR_TYPES; i++)
_armor[i] = NULL;
_weapon1 = NULL;
_weapon2 = NULL;
// Create native weapons
if (!_nativeWeapon1.empty() && !_operativeVerb1.empty()) {
_weapon1 = new Obj;
_weapon1->_name = _nativeWeapon1;
_weapon1->_operativeVerb = _operativeVerb1;
_weapon1->_type = Obj::REGULAR_WEAPON;
_weapon1->_accuracy = 0;
_weapon1->_damage = _weaponDamage1;
_weapon1->_sound = _weaponSound1;
}
if (!_nativeWeapon2.empty() && !_operativeVerb2.empty()) {
_weapon2 = new Obj;
_weapon2->_name = _nativeWeapon2;
_weapon2->_operativeVerb = _operativeVerb2;
_weapon2->_type = Obj::REGULAR_WEAPON;
_weapon2->_accuracy = 0;
_weapon2->_damage = _weaponDamage2;
_weapon2->_sound = _weaponSound2;
}
delete data;
}
Chr::~Chr() {
delete _weapon1;
delete _weapon2;
}
void Chr::resetState() {
_context._statVariables[PHYS_STR_BAS] = _context._statVariables[PHYS_STR_CUR] = _physicalStrength;
_context._statVariables[PHYS_HIT_BAS] = _context._statVariables[PHYS_HIT_CUR] = _physicalHp;
_context._statVariables[PHYS_ARM_BAS] = _context._statVariables[PHYS_ARM_CUR] = _naturalArmor;
_context._statVariables[PHYS_ACC_BAS] = _context._statVariables[PHYS_ACC_CUR] = _physicalAccuracy;
_context._statVariables[SPIR_STR_BAS] = _context._statVariables[SPIR_STR_CUR] = _spiritualStength;
_context._statVariables[SPIR_HIT_BAS] = _context._statVariables[SPIR_HIT_CUR] = _spiritialHp;
_context._statVariables[SPIR_ARM_BAS] = _context._statVariables[SPIR_ARM_CUR] = _naturalArmor;
_context._statVariables[SPIR_ACC_BAS] = _context._statVariables[SPIR_ACC_CUR] = _physicalAccuracy;
_context._statVariables[PHYS_SPE_BAS] = _context._statVariables[PHYS_SPE_CUR] = _runningSpeed;
}
ObjArray *Chr::getWeapons(bool includeMagic) {
ObjArray *list = new ObjArray;
if (_weapon1)
list->push_back(_weapon1);
if (_weapon2)
list->push_back(_weapon2);
for (uint i = 0; i < _inventory.size(); i++)
switch (_inventory[i]->_type) {
case Obj::REGULAR_WEAPON:
case Obj::THROW_WEAPON:
list->push_back(_inventory[i]);
break;
case Obj::MAGICAL_OBJECT:
if (includeMagic)
list->push_back(_inventory[i]);
break;
default:
break;
}
return list;
}
ObjArray *Chr::getMagicalObjects() {
ObjArray *list = new ObjArray;
for (uint i = 0; i < _inventory.size(); i++)
if (_inventory[i]->_type == Obj::MAGICAL_OBJECT)
list->push_back(_inventory[i]);
return list;
}
void Chr::wearObjs() {
for (uint i = 0; i < _inventory.size(); i++)
wearObjIfPossible(_inventory[i]);
}
int Chr::wearObjIfPossible(Obj *obj) {
switch (obj->_type) {
case Obj::HELMET:
if (_armor[HEAD_ARMOR] == NULL) {
_armor[HEAD_ARMOR] = obj;
return Chr::HEAD_ARMOR;
}
break;
case Obj::CHEST_ARMOR:
if (_armor[BODY_ARMOR] == NULL) {
_armor[BODY_ARMOR] = obj;
return Chr::BODY_ARMOR;
}
break;
case Obj::SHIELD:
if (_armor[SHIELD_ARMOR] == NULL) {
_armor[SHIELD_ARMOR] = obj;
return Chr::SHIELD_ARMOR;
}
break;
case Obj::SPIRITUAL_ARMOR:
if (_armor[MAGIC_ARMOR] == NULL) {
_armor[MAGIC_ARMOR] = obj;
return Chr::MAGIC_ARMOR;
}
break;
default:
return -1;
}
return -1;
}
const char *Chr::getDefiniteArticle(bool capitalize) {
if (!_nameProperNoun)
return capitalize ? "The " : "the ";
return "";
}
bool Chr::isWearing(Obj *obj) {
for (int i = 0; i < NUMBER_OF_ARMOR_TYPES; i++)
if (_armor[i] == obj)
return true;
return false;
}
} // End of namespace Wage

343
engines/wage/entities.h Normal file
View File

@@ -0,0 +1,343 @@
/* 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/>.
*
* MIT License:
*
* Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#ifndef WAGE_ENTITIES_H
#define WAGE_ENTITIES_H
namespace Graphics {
class ManagedSurface;
class MacFont;
}
namespace Wage {
class Design;
class Script;
enum StatVariable {
/** The base physical accuracy of the player. */
PHYS_ACC_BAS = 0,
/** The current physical accuracy of the player. */
PHYS_ACC_CUR = 1,
/** The base physical armor of the player. */
PHYS_ARM_BAS = 2,
/** The current physical armor of the player. */
PHYS_ARM_CUR = 3,
/** The base physical hit points of the player. */
PHYS_HIT_BAS = 4,
/** The current physical hit points of the player. */
PHYS_HIT_CUR = 5,
/** The base physical speed of the player. */
PHYS_SPE_BAS = 6,
/** The current physical speed of the player. */
PHYS_SPE_CUR = 7,
/** The base physical strength of the player. */
PHYS_STR_BAS = 8,
/** The current physical strength of the player. */
PHYS_STR_CUR = 9,
/** The base spiritual accuracy of the player. */
SPIR_ACC_BAS = 10,
/** The current spiritual accuracy of the player. */
SPIR_ACC_CUR = 11,
/** The base spiritual armor of the player. */
SPIR_ARM_BAS = 12,
/** The current spiritual armor of the player. */
SPIR_ARM_CUR = 13,
/** The base spiritual hit points of the player. */
SPIR_HIT_BAS = 14,
/** The current spiritual hit points of the player. */
SPIR_HIT_CUR = 15,
/** The base spiritual strength of the player. */
SPIR_STR_BAS = 16,
/** The current spiritual strength of the player. */
SPIR_STR_CUR = 17
};
class Context {
public:
Context();
int16 _visits; // Number of scenes visited, including repeated visits
int16 _kills; // Number of characters killed
int16 _experience;
bool _frozen;
int16 _freezeTimer;
int16 _userVariables[26 * 9];
int16 _statVariables[18];
};
class Designed {
public:
Designed() : _design(NULL), _designBounds(NULL), _classType(UNKNOWN) {}
~Designed();
Common::String _name;
Design *_design;
Common::Rect *_designBounds;
OperandType _classType;
Common::Rect *getDesignBounds() {
return _designBounds == NULL ? NULL : new Common::Rect(*_designBounds);
}
void setDesignBounds(Common::Rect *bounds);
Common::String toString() const { return _name; }
};
class Chr : public Designed {
public:
enum ChrDestination {
RETURN_TO_STORAGE = 0,
RETURN_TO_RANDOM_SCENE = 1,
RETURN_TO_INITIAL_SCENE = 2
};
enum ChrPart {
HEAD = 0,
CHEST = 1,
SIDE = 2
};
enum ChrArmorType {
HEAD_ARMOR = 0,
BODY_ARMOR = 1,
SHIELD_ARMOR = 2,
MAGIC_ARMOR = 3,
NUMBER_OF_ARMOR_TYPES = 4
};
Chr(Common::String name, Common::SeekableReadStream *data);
~Chr();
int _index;
int _resourceId;
Common::String _initialScene;
int _gender;
bool _nameProperNoun;
bool _playerCharacter;
uint _maximumCarriedObjects;
int _returnTo;
int _physicalStrength;
int _physicalHp;
int _naturalArmor;
int _physicalAccuracy;
int _spiritualStength;
int _spiritialHp;
int _resistanceToMagic;
int _spiritualAccuracy;
int _runningSpeed;
uint _rejectsOffers;
int _followsOpponent;
Common::String _initialSound;
Common::String _scoresHitSound;
Common::String _receivesHitSound;
Common::String _dyingSound;
Common::String _nativeWeapon1;
Common::String _operativeVerb1;
int _weaponDamage1;
Common::String _weaponSound1;
Common::String _nativeWeapon2;
Common::String _operativeVerb2;
int _weaponDamage2;
Common::String _weaponSound2;
int _winningWeapons;
int _winningMagic;
int _winningRun;
int _winningOffer;
int _losingWeapons;
int _losingMagic;
int _losingRun;
int _losingOffer;
Common::String _initialComment;
Common::String _scoresHitComment;
Common::String _receivesHitComment;
Common::String _makesOfferComment;
Common::String _rejectsOfferComment;
Common::String _acceptsOfferComment;
Common::String _dyingWords;
Scene *_currentScene;
ObjArray _inventory;
Obj *_armor[NUMBER_OF_ARMOR_TYPES];
Context _context;
ObjArray *getWeapons(bool includeMagic);
ObjArray *getMagicalObjects();
const char *getDefiniteArticle(bool capitalize);
Obj *_weapon1;
Obj *_weapon2;
public:
int wearObjIfPossible(Obj *obj);
void wearObjs();
void resetState();
bool isWearing(Obj *obj);
};
class Obj : public Designed {
public:
Obj();
Obj(Common::String name, Common::SeekableReadStream *data, int resourceId);
~Obj();
enum ObjectType {
REGULAR_WEAPON = 1,
THROW_WEAPON = 2,
MAGICAL_OBJECT = 3,
HELMET = 4,
SHIELD = 5,
CHEST_ARMOR = 6,
SPIRITUAL_ARMOR = 7,
MOBILE_OBJECT = 8,
IMMOBILE_OBJECT = 9
};
enum AttackType {
CAUSES_PHYSICAL_DAMAGE = 0,
CAUSES_SPIRITUAL_DAMAGE = 1,
CAUSES_PHYSICAL_AND_SPIRITUAL_DAMAGE = 2,
HEALS_PHYSICAL_DAMAGE = 3,
HEALS_SPIRITUAL_DAMAGE = 4,
HEALS_PHYSICAL_AND_SPIRITUAL_DAMAGE = 5,
FREEZES_OPPONENT = 6
};
public:
int _index;
int _resourceId;
bool _namePlural;
uint _value;
int _attackType;
int _numberOfUses;
bool _returnToRandomScene;
Common::String _sceneOrOwner;
Common::String _clickMessage;
Common::String _failureMessage;
Common::String _useMessage;
Scene *_currentScene;
Chr *_currentOwner;
int _type;
uint _accuracy;
Common::String _operativeVerb;
int _damage;
Common::String _sound;
public:
void setCurrentOwner(Chr *currentOwner) {
_currentOwner = currentOwner;
if (currentOwner != NULL)
_currentScene = NULL;
}
void setCurrentScene(Scene *currentScene) {
_currentScene = currentScene;
if (currentScene != NULL)
_currentOwner = NULL;
}
Chr *removeFromChr();
Designed *removeFromCharOrScene();
void resetState(Chr *owner, Scene *scene);
};
class Scene : public Designed {
public:
enum SceneTypes {
PERIODIC = 0,
RANDOM = 1
};
int _resourceId;
Script *_script;
Common::String _text;
Common::Rect *_textBounds;
Graphics::MacFont *_font;
bool _blocked[4];
Common::String _messages[4];
int _soundFrequency; // times a minute, max 3600
int _soundType;
Common::String _soundName;
int _worldX;
int _worldY;
bool _visited;
ObjList _objs;
ChrList _chrs;
Scene();
Scene(Common::String name, Common::SeekableReadStream *data);
~Scene();
Designed *lookUpEntity(int x, int y);
Common::Rect *getTextBounds() {
return _textBounds == NULL ? NULL : new Common::Rect(*_textBounds);
}
void paint(Graphics::ManagedSurface *screen, int x, int y);
const Graphics::MacFont *getFont() { return _font; }
};
} // End of namespace Wage
#endif

674
engines/wage/gui.cpp Normal file
View File

@@ -0,0 +1,674 @@
/* 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/>.
*
* MIT License:
*
* Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#include "common/timer.h"
#include "common/system.h"
#include "common/config-manager.h"
#include "audio/softsynth/pcspk.h"
#include "graphics/primitives.h"
#include "graphics/macgui/macfontmanager.h"
#include "graphics/macgui/macdialog.h"
#include "graphics/macgui/macwindowmanager.h"
#include "graphics/macgui/macwindow.h"
#include "graphics/macgui/macmenu.h"
#include "wage/wage.h"
#include "wage/design.h"
#include "wage/entities.h"
#include "wage/gui.h"
#include "wage/world.h"
namespace Wage {
static const Graphics::MacMenuData menuSubItems[] = {
{ kMenuHighLevel, "File", 0, 0, false },
{ kMenuHighLevel, "Edit", 0, 0, false },
{ kMenuFile, "New", kMenuActionNew, 0, true },
{ kMenuFile, "Open...", kMenuActionOpen, 0, true },
{ kMenuFile, "Close", kMenuActionClose, 0, false },
{ kMenuFile, "Save", kMenuActionSave, 0, false },
{ kMenuFile, "Save as...", kMenuActionSaveAs, 0, true },
{ kMenuFile, "Revert", kMenuActionRevert, 0, false },
{ kMenuFile, "Quit", kMenuActionQuit, 0, true },
{ kMenuEdit, "Undo", kMenuActionUndo, 'Z', false },
{ kMenuEdit, NULL, 0, 0, false },
{ kMenuEdit, "Cut", kMenuActionCut, 'K', false },
{ kMenuEdit, "Copy", kMenuActionCopy, 'C', false },
{ kMenuEdit, "Paste", kMenuActionPaste, 'V', false },
{ kMenuEdit, "Clear", kMenuActionClear, 'B', false },
{ 0, NULL, 0, 0, false }
};
static bool consoleWindowCallback(WindowClick click, Common::Event &event, void *gui);
static bool sceneWindowCallback(WindowClick click, Common::Event &event, void *gui);
static void menuCommandsCallback(int action, Common::String &text, void *data);
Gui::Gui(WageEngine *engine) {
_engine = engine;
_scene = NULL;
_sceneDirty = true;
_screen.create(g_system->getWidth(), g_system->getHeight(), Graphics::PixelFormat::createFormatCLUT8());
_wm = new Graphics::MacWindowManager(Graphics::kWMNoScummVMWallpaper);
_wm->_fontMan->loadFonts(Common::Path(engine->getGameFile()));
_wm->setScreen(&_screen);
_menu = _wm->addMenu();
_menu->setCommandsCallback(menuCommandsCallback, this);
_menu->addStaticMenus(menuSubItems);
_menu->addSubMenu(nullptr, kMenuAbout);
_menu->addMenuItem(_menu->getSubmenu(nullptr, kMenuAbout), _engine->_world->getAboutMenuItemName(), kMenuActionAbout);
_commandsMenuId = _menu->addMenuItem(nullptr, _engine->_world->_commandsMenuName);
regenCommandsMenu();
if (!_engine->_world->_weaponMenuDisabled) {
_weaponsMenuId = _menu->addMenuItem(nullptr, _engine->_world->_weaponsMenuName);
regenWeaponsMenu();
} else {
_weaponsMenuId = -1;
}
_menu->calcDimensions();
if (g_system->hasTextInClipboard()) {
_menu->enableCommand(kMenuEdit, kMenuActionPaste, true);
}
_sceneWindow = _wm->addWindow(false, false, false);
_sceneWindow->setCallback(sceneWindowCallback, this);
//TODO: Make the font we use here work
// (currently MacFontRun::getFont gets called with the fonts being uninitialized,
// so it initializes them by itself with default params, and not those here)
const Graphics::MacFont *font = new Graphics::MacFont(Graphics::kMacFontSystem, 8);
uint maxWidth = _screen.w;
_consoleWindow = _wm->addTextWindow(font, kColorBlack, kColorWhite, maxWidth, Graphics::kTextAlignLeft, _menu, 4);
_consoleWindow->setCallback(consoleWindowCallback, this);
_consoleWindow->setBorderColor(kColorWhite);
_consoleWindow->setEditable(true);
_selectedMenuItem = -1;
loadBorders();
}
Gui::~Gui() {
_screen.free();
_console.free();
delete _wm;
}
void Gui::draw() {
if (_engine->_isGameOver) {
_wm->draw();
return;
}
if (!_engine->_world->_player->_currentScene)
return;
if (_scene != _engine->_world->_player->_currentScene) {
_sceneDirty = true;
_scene = _engine->_world->_player->_currentScene;
Common::Rect sceneBounds = *_scene->_designBounds;
const Graphics::BorderOffsets &offsets = _sceneWindow->getBorderOffsets();
int maxTitleWidth = sceneBounds.width() - (kWindowMinWidth - offsets.right);
Common::String displayTitle = _scene->_name;
if (maxTitleWidth > 0) {
const Graphics::Font *titleFont = getTitleFont();
if (titleFont) {
// keep deleting the last character untill the title fits
while (displayTitle.size() > 0 && titleFont->getStringWidth(displayTitle) > maxTitleWidth) {
displayTitle.deleteLastChar();
}
}
} else {
displayTitle.clear();
}
_sceneWindow->setTitle(displayTitle);
_sceneWindow->setDimensions(sceneBounds);
_consoleWindow->setDimensions(*_scene->_textBounds);
_wm->setActiveWindow(_consoleWindow->getId());
_wm->setFullRefresh(true);
}
drawScene();
_wm->draw();
_sceneDirty = false;
}
void Gui::drawScene() {
if (!_sceneDirty)
return;
_scene->paint(_sceneWindow->getWindowSurface(), 0, 0);
_sceneWindow->setDirty(true);
_sceneDirty = true;
_menu->setDirty(true);
}
static bool consoleWindowCallback(WindowClick click, Common::Event &event, void *g) {
Gui *gui = (Gui *)g;
return gui->processConsoleEvents(click, event);
}
bool Gui::processConsoleEvents(WindowClick click, Common::Event &event) {
if (click == kBorderCloseButton && event.type == Common::EVENT_LBUTTONUP) {
_engine->quitGame();
return true;
}
return false;
}
static bool sceneWindowCallback(WindowClick click, Common::Event &event, void *g) {
Gui *gui = (Gui *)g;
return gui->processSceneEvents(click, event);
}
bool Gui::processSceneEvents(WindowClick click, Common::Event &event) {
if (click == kBorderInner && event.type == Common::EVENT_LBUTTONUP) {
Designed *obj = _scene->lookUpEntity(event.mouse.x, event.mouse.y);
if (obj != nullptr)
_engine->processTurn(NULL, obj);
return true;
}
if (click == kBorderCloseButton && event.type == Common::EVENT_LBUTTONUP) {
_engine->quitGame();
return true;
}
if (event.type == Common::EVENT_KEYDOWN) {
return _consoleWindow->processEvent(event);
}
return false;
}
////////////////
// Menu stuff
////////////////
void Gui::regenCommandsMenu() {
_menu->createSubMenuFromString(_commandsMenuId, _engine->_world->_commandsMenu.c_str(), kMenuActionCommand);
}
void Gui::regenWeaponsMenu() {
if (_engine->_world->_weaponMenuDisabled)
return;
_menu->clearSubMenu(_weaponsMenuId);
Chr *player = _engine->_world->_player;
if (!player) {
warning("regenWeaponsMenu: player is not defined");
return;
}
ObjArray *weapons = player->getWeapons(true);
bool empty = true;
Graphics::MacMenuSubMenu *submenu = _menu->getSubmenu(nullptr, _weaponsMenuId);
if (submenu == nullptr)
submenu = _menu->addSubMenu(nullptr, _weaponsMenuId);
for (uint i = 0; i < weapons->size(); i++) {
Obj *obj = (*weapons)[i];
if (obj->_type == Obj::REGULAR_WEAPON ||
obj->_type == Obj::THROW_WEAPON ||
obj->_type == Obj::MAGICAL_OBJECT) {
Common::String command(obj->_operativeVerb);
command += " ";
command += obj->_name;
_menu->addMenuItem(submenu, command, kMenuActionCommand, 0, 0, true);
empty = false;
}
}
delete weapons;
if (empty)
_menu->addMenuItem(submenu, "You have no weapons", 0, 0, 0, false);
}
bool Gui::processEvent(Common::Event &event) {
if (event.type == Common::EVENT_CLIPBOARD_UPDATE) {
_menu->enableCommand(kMenuEdit, kMenuActionPaste, true);
}
if (event.type == Common::EVENT_MOUSEMOVE) {
bool mouseOnItem = false;
for (int i = 0; i < _menu->numberOfMenus(); i++) {
Graphics::MacMenuItem *menuItem = _menu->getMenuItem(i);
if (menuItem->enabled && menuItem->bbox.contains(event.mouse.x, event.mouse.y)) {
if (_selectedMenuItem != i) {
_engine->sayText(menuItem->text, Common::TextToSpeechManager::INTERRUPT);
_selectedMenuItem = i;
}
mouseOnItem = true;
break;
}
}
if (!mouseOnItem) {
_selectedMenuItem = -1;
}
}
if (event.type == Common::EVENT_KEYDOWN) {
_wm->setActiveWindow(_consoleWindow->getId());
}
return _wm->processEvent(event);
}
void menuCommandsCallback(int action, Common::String &text, void *data) {
Gui *g = (Gui *)data;
g->executeMenuCommand(action, text);
}
void Gui::executeMenuCommand(int action, Common::String &text) {
switch(action) {
case kMenuActionAbout:
aboutDialog();
break;
case kMenuActionNew:
_engine->_restartRequested = true;
break;
case kMenuActionClose:
// This is a no-op as we do not let new game to be opened
break;
case kMenuActionRevert:
if (_engine->_defaultSaveSlot != -1) {
_engine->_isGameOver = false;
_engine->_world->_weaponMenuDisabled = false;
_engine->loadGameState(_engine->_defaultSaveSlot);
_scene = nullptr; // To force current scene to be redrawn
_engine->redrawScene();
g_system->updateScreen();
}
break;
case kMenuActionOpen:
_engine->scummVMSaveLoadDialog(false);
break;
case kMenuActionQuit:
_engine->quitGame();
break;
case kMenuActionSave:
_engine->saveGame();
break;
case kMenuActionSaveAs:
_engine->scummVMSaveLoadDialog(true);
break;
case kMenuActionUndo:
actionUndo();
break;
case kMenuActionCut:
actionCut();
break;
case kMenuActionCopy:
actionCopy();
break;
case kMenuActionPaste:
actionPaste();
break;
case kMenuActionClear:
actionClear();
break;
case kMenuActionCommand: {
_engine->_inputText = text;
Common::String inp = text + '\n';
_engine->sayText(text, Common::TextToSpeechManager::QUEUE);
appendText(inp.c_str());
_consoleWindow->clearInput();
_engine->processTurn(&text, NULL);
break;
}
default:
warning("Unknown action: %d", action);
}
}
//////////////////
// Console stuff
//////////////////
const Graphics::MacFont *Gui::getConsoleMacFont() {
Scene *scene = _engine->_world->_player->_currentScene;
return scene->getFont();
}
const Graphics::Font *Gui::getConsoleFont() {
return _wm->_fontMan->getFont(*getConsoleMacFont());
}
const Graphics::Font *Gui::getTitleFont() {
return _wm->_fontMan->getFont(Graphics::MacFont(Graphics::kMacFontSystem, 12));
}
void Gui::appendText(const char *s) {
_consoleWindow->appendText(s, getConsoleMacFont());
}
void Gui::clearOutput() {
_consoleWindow->clearText();
}
void Gui::actionCopy() {
g_system->setTextInClipboard(Common::convertUtf32ToUtf8(_consoleWindow->getSelection()));
_menu->enableCommand(kMenuEdit, kMenuActionPaste, true);
}
void Gui::actionPaste() {
if (g_system->hasTextInClipboard()) {
_undobuffer = _engine->_inputText;
_consoleWindow->appendInput(g_system->getTextFromClipboard());
_menu->enableCommand(kMenuEdit, kMenuActionUndo, true);
}
}
void Gui::actionUndo() {
_consoleWindow->clearInput();
_consoleWindow->appendInput(_undobuffer);
_menu->enableCommand(kMenuEdit, kMenuActionUndo, false);
}
void Gui::actionClear() {
if (_consoleWindow->getSelectedText()->endY == -1)
return;
Common::String input = Common::convertFromU32String(_consoleWindow->getInput());
_consoleWindow->cutSelection();
_undobuffer = input;
_menu->enableCommand(kMenuEdit, kMenuActionUndo, true);
}
void Gui::actionCut() {
if (_consoleWindow->getSelectedText()->endY == -1)
return;
Common::String input = Common::convertFromU32String(_consoleWindow->getInput());
g_system->setTextInClipboard(_consoleWindow->cutSelection());
_undobuffer = input;
_menu->enableCommand(kMenuEdit, kMenuActionUndo, true);
_menu->enableCommand(kMenuEdit, kMenuActionPaste, true);
}
void Gui::disableUndo() {
_menu->enableCommand(kMenuEdit, kMenuActionUndo, false);
}
void Gui::disableAllMenus() {
_menu->disableAllMenus();
}
void Gui::enableNewGameMenus() {
_menu->enableCommand(kMenuFile, kMenuActionNew, true);
_menu->enableCommand(kMenuFile, kMenuActionOpen, true);
_menu->enableCommand(kMenuFile, kMenuActionQuit, true);
}
void Gui::enableSave() {
_menu->enableCommand(kMenuFile, kMenuActionSave, true);
_menu->enableCommand(kMenuFile, kMenuActionSaveAs, true);
}
void Gui::enableRevert() {
_menu->enableCommand(kMenuFile, kMenuActionRevert, true);
}
class AboutDialog : public Graphics::MacDialog {
public:
AboutDialog(Graphics::ManagedSurface *screen, Graphics::MacWindowManager *wm, int width, Graphics::MacText *mactext, int maxTextWidth, Graphics::MacDialogButtonArray *buttons, uint defaultButton);
virtual ~AboutDialog() {
if (_volumeChanged)
ConfMan.flushToDisk();
}
virtual void paint() override;
virtual bool processEvent(const Common::Event &event) override;
private:
Common::Rect _volBbox;
const int kVolWidth = 160;
bool _volumeChanged = false;
};
AboutDialog::AboutDialog(Graphics::ManagedSurface *screen, Graphics::MacWindowManager *wm, int width, Graphics::MacText *mactext, int maxTextWidth, Graphics::MacDialogButtonArray *buttons, uint defaultButton)
: Graphics::MacDialog(screen, wm, width, mactext, maxTextWidth, buttons, defaultButton) {
_volBbox = Common::Rect(0, 0, kVolWidth, 12);
_volBbox.moveTo(_bbox.left + (_bbox.width() - kVolWidth) / 2, _bbox.bottom - 32);
}
void AboutDialog::paint() {
Graphics::MacDialog::paint();
const char *volumeText = "- Volume +";
int w = _font->getStringWidth(volumeText);
int x = _bbox.left + (_bbox.width() - w) / 2;
int y = _bbox.bottom - 52;
_font->drawString(_screen, volumeText, x, y, _bbox.width(), kColorBlack);
uint32 volume = ConfMan.getInt("sfx_volume");
Graphics::Primitives &primitives = _wm->getDrawPrimitives();
Common::Rect volBox(0, 0, volume * kVolWidth / 256, 12);
volBox.moveTo(_bbox.left + (_bbox.width() - kVolWidth) / 2, _bbox.bottom - 32);
Graphics::MacPlotData pd(_screen, nullptr, &_wm->getPatterns(), 1, 0, 0, 1, _wm->_colorBlack, false);
primitives.drawFilledRect1(volBox, kColorBlack, &pd);
primitives.drawRect1(_volBbox, kColorBlack, &pd);
}
bool AboutDialog::processEvent(const Common::Event &event) {
if (event.type == Common::EVENT_LBUTTONUP) {
if (_volBbox.contains(event.mouse.x, event.mouse.y)) {
int delta = event.mouse.x - _volBbox.left;
int volume = delta * 256 / kVolWidth;
ConfMan.setInt("sfx_volume", volume);
_volumeChanged = true;
_needsRedraw = true;
g_wage->syncSoundSettings();
g_wage->_speaker->play(Audio::PCSpeaker::kWaveFormSquare, 500, 150);
return true;
}
}
return false;
}
void Gui::aboutDialog() {
Common::U32String messageText(_engine->_world->_aboutMessage, Common::kMacRoman);
Common::U32String disclaimer("\n\n\n\nThis adventure was produced with World Builder\xAA\nthe adventure game creation system.\n\xA9 Copyright 1986 by William C. Appleton, All Right Reserved\nPublished by Silicon Beach Software, Inc.", Common::kMacRoman);
_engine->sayText(_engine->_world->_aboutMessage);
_engine->sayText(disclaimer, Common::TextToSpeechManager::QUEUE);
messageText += disclaimer;
Graphics::MacFont font(Graphics::kMacFontGeneva, 9, 0);
Graphics::MacText aboutMessage(messageText, _wm, &font, Graphics::kColorBlack,
Graphics::kColorWhite, 400, Graphics::kTextAlignCenter);
Graphics::MacDialogButtonArray buttons;
buttons.push_back(new Graphics::MacDialogButton("OK", 191, aboutMessage.getTextHeight() + 30, 68, 28));
// add a dummy button to push volume slider position down
// to avoid the overlapping of volume slider with OK button in the about section
buttons.push_back(new Graphics::MacDialogButton("", 0, aboutMessage.getTextHeight() + 100, 0, 0));
AboutDialog about(&_screen, _wm, 450, &aboutMessage, 400, &buttons, 0);
delete buttons.back();
buttons.pop_back();
// close the menu before calling run because it blocks execution
if (_menu)
_menu->closeMenu();
int button = about.run();
if (button == Graphics::kMacDialogQuitRequested)
_engine->_shouldQuit = true;
}
void Gui::gameOver() {
Graphics::MacDialogButtonArray buttons;
buttons.push_back(new Graphics::MacDialogButton("OK", 66, 67, 68, 28));
Graphics::MacFont font;
Graphics::MacText gameOverMessage(*_engine->_world->_gameOverMessage, _wm, &font, Graphics::kColorBlack,
Graphics::kColorWhite, 199, Graphics::kTextAlignCenter);
_engine->sayText(*_engine->_world->_gameOverMessage, Common::TextToSpeechManager::QUEUE);
Graphics::MacDialog gameOverDialog(&_screen, _wm, 199, &gameOverMessage, 199, &buttons, 0);
int button = gameOverDialog.run();
if (button == Graphics::kMacDialogQuitRequested)
_engine->_shouldQuit = true;
_engine->doClose();
disableAllMenus();
enableNewGameMenus();
}
bool Gui::saveDialog() {
Graphics::MacDialogButtonArray buttons;
buttons.push_back(new Graphics::MacDialogButton("No", 19, 67, 68, 28));
buttons.push_back(new Graphics::MacDialogButton("Yes", 112, 67, 68, 28));
buttons.push_back(new Graphics::MacDialogButton("Cancel", 205, 67, 68, 28));
Graphics::MacFont font;
Graphics::MacText saveBeforeCloseMessage(*_engine->_world->_saveBeforeCloseMessage, _wm, &font, Graphics::kColorBlack,
Graphics::kColorWhite, 250, Graphics::kTextAlignCenter);
_engine->sayText(*_engine->_world->_saveBeforeCloseMessage);
Graphics::MacDialog save(&_screen, _wm, 291, &saveBeforeCloseMessage, 250, &buttons, 1);
int button = save.run();
if (button == Graphics::kMacDialogQuitRequested)
_engine->_shouldQuit = true;
else if (button == 2) // Cancel
return false;
else if (button == 1)
_engine->saveGame();
_engine->doClose();
return true;
}
} // End of namespace Wage

185
engines/wage/gui.h Normal file
View File

@@ -0,0 +1,185 @@
/* 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/>.
*
* MIT License:
*
* Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#ifndef WAGE_GUI_H
#define WAGE_GUI_H
#include "common/str-array.h"
#include "graphics/font.h"
#include "graphics/managed_surface.h"
#include "graphics/macgui/macwindowmanager.h"
#include "graphics/macgui/mactextwindow.h"
#include "graphics/macgui/macwindow.h"
#include "graphics/macgui/mactext.h"
#include "graphics/macgui/macmenu.h"
#include "graphics/macgui/macwindowborder.h"
#include "common/events.h"
#include "common/rect.h"
#include "common/file.h"
#include "graphics/pixelformat.h"
#include "image/bmp.h"
namespace Wage {
using namespace Graphics::MacWindowConstants;
class Scene;
class WageEngine;
enum {
kCursorHeight = 12
};
enum {
kFontStyleBold = 1,
kFontStyleItalic = 2,
kFontStyleUnderline = 4,
kFontStyleOutline = 8,
kFontStyleShadow = 16,
kFontStyleCondensed = 32,
kFontStyleExtended = 64
};
enum {
kMenuHighLevel = -1,
kMenuAbout = 0,
kMenuFile = 1,
kMenuEdit = 2,
kMenuCommands = 3,
kMenuWeapons = 4
};
enum {
kMenuActionAbout,
kMenuActionNew,
kMenuActionOpen,
kMenuActionClose,
kMenuActionSave,
kMenuActionSaveAs,
kMenuActionRevert,
kMenuActionQuit,
kMenuActionUndo,
kMenuActionCut,
kMenuActionCopy,
kMenuActionPaste,
kMenuActionClear,
kMenuActionCommand
};
class Gui {
public:
Gui(WageEngine *engine);
~Gui();
void draw();
void appendText(const char *str);
bool processEvent(Common::Event &event);
void setSceneDirty() { _sceneDirty = true; }
void regenCommandsMenu();
void regenWeaponsMenu();
void actionCopy();
void actionPaste();
void actionUndo();
void actionClear();
void actionCut();
void disableUndo();
void disableAllMenus();
void enableNewGameMenus();
void enableSave();
void enableRevert();
bool processSceneEvents(WindowClick click, Common::Event &event);
bool processConsoleEvents(WindowClick click, Common::Event &event);
void executeMenuCommand(int action, Common::String &text);
void clearOutput();
void gameOver();
bool saveDialog();
void aboutDialog();
private:
void drawScene();
const Graphics::MacFont *getConsoleMacFont();
const Graphics::Font *getConsoleFont();
const Graphics::Font *getTitleFont();
void loadBorders();
void loadBorder(Graphics::MacWindow *target, const char *border[], uint height, uint32 flags, int titlePos = 0);
public:
Graphics::ManagedSurface _screen;
WageEngine *_engine;
Scene *_scene;
Graphics::MacWindowManager *_wm;
Graphics::MacWindow *_sceneWindow;
Graphics::MacTextWindow *_consoleWindow;
private:
Graphics::ManagedSurface _console;
Graphics::MacMenu *_menu;
bool _sceneDirty;
Common::String _undobuffer;
int _commandsMenuId;
int _weaponsMenuId;
int _selectedMenuItem;
};
} // End of namespace Wage
#endif

304
engines/wage/guiborders.cpp Normal file
View File

@@ -0,0 +1,304 @@
/* 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/>.
*
* MIT License..
*
* Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions..
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#include "wage/wage.h"
#include "wage/gui.h"
namespace Wage {
// Original files sit in engines/wage/guiborders
// to print out similar pictures, use
// bmpDecoder.getSurface()->debugPrint(0, 0, 0, 0, 0, 1);
// in MacWindowBorder::loadBorder()
//
// TODO: Store these as XPM files?
static const char *wage_border_inact_title[] = {
"...................................... .. .. ......................................",
".. .. .. ..",
".. ################################ .. ## .. ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ## ## ################################ ..",
".. ################################ ## ## ## ################################ ..",
".. ################################ ## ## ## ################################ ..",
".. ################################ ## ## ## ################################ ..",
".. ################################ ## ## ## ################################ ..",
".. ################################ ## ## ## ################################ ..",
".. ################################ ## ## ## ################################ ..",
".. ################################ ## ## ## ################################ ..",
".. ################################ ## ## ## ################################ ..",
".. ################################ ## ## ## ################################ ..",
".. ################################ ## ## ## ################################ ..",
".. ################################ ## ## ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ .. ## .. ################################ ",
".. .. .. ",
" .... ######################## .................. ######################## .... ",
".. .......... ",
".. ################################ .......... ################################ ",
".. ################################ ################################ ..",
".. ################################ ########## ################################ ..",
".. ################################ ########## ################################ ..",
".. ################################ ########## ################################ ..",
".. ################################ ########## ################################ ..",
".. ################################ ########## ################################ ..",
".. ################################ ########## ################################ ..",
".. ################################ ########## ################################ ..",
".. ################################ ########## ################################ ..",
".. ################################ ########## ################################ ..",
".. ################################ ########## ################################ ..",
".. ################################ ########## ################################ ..",
".. ################################ ########## ################################ ..",
".. ################################ ################################ ..",
".. ################################ .......... ################################ ..",
".. .......... ..",
".................................. .................................."};
static const char *wage_border_act_noscrollbar_title[] = {
"...................................... .. .. ......................................",
".. .. .. ..",
".. ################################ .. ## .. ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ## ## ################################ ..",
".. ################################ ## ## ## ################################ ..",
".. ######## ######## ## ################################ ..",
".. ######## ############ ######## ## ################################ ..",
".. ######## ############ ######## ## ################################ ..",
".. ######## ############ ######## ## ################################ ..",
".. ######## ############ ######## ## ################################ ..",
".. ######## ############ ######## ## ################################ ..",
".. ######## ############ ######## ## ################################ ..",
".. ######## ######## ## ################################ ..",
".. ################################ ## ## ## ################################ ..",
".. ################################ ## ## ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ .. ## .. ################################ ",
".. .. .. ",
" .... #### #### .................. #### #### .... ",
".. .......... ",
".. ################################ .......... ################################ ",
".. ################################ ################################ ..",
".. ################################ ########## ################################ ..",
".. ################################ ########## ################################ ..",
".. ################################ ################################ ..",
".. ################################ ################################ ..",
".. ################################ ################################ ..",
".. ################################ ################################ ..",
".. ################################ ################################ ..",
".. ################################ ################################ ..",
".. ################################ ################################ ..",
".. ################################ ################################ ..",
".. ################################ ########## ################################ ..",
".. ################################ ########## ################################ ..",
".. ################################ ################################ ..",
".. ################################ .......... ################################ ..",
".. .......... ..",
".................................. .................................."};
static const char *wage_border_inact[] = {
"...................................... ......................................",
".. .. ..",
".. ################################ .. ################################ ..",
".. ################################ ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ################################ ..",
".. ################################ .. ################################ ",
".. .. ",
" .... ######################## .......... ######################## .... ",
".. .. ",
".. ################################ .. ################################ ",
".. ################################ ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ################################ ..",
".. ################################ .. ################################ ..",
".. .. ..",
".................................. .................................."};
static const char *wage_border_act[] = {
"...................................... ......................................",
".. .. ..",
".. ################################ .. ################################ ..",
".. ################################ ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ######## ######## ################################ ..",
".. ######## ############ ######## ################################ ..",
".. ######## ############ ######## ################################ ..",
".. ######## ############ ######## ################################ ..",
".. ######## ############ ######## ################################ ..",
".. ######## ############ ######## ################################ ..",
".. ######## ############ ######## ################################ ..",
".. ######## ######## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ################################ ..",
".. ################################ .. ################################ ",
".. .. ",
"...... #### #### .......... ########## ########## .... ",
"...... #### #### .......... ######## ######## .... ",
"...... #### #### .......... ###### ###### .... ",
"...... #### #### .......... #### #### .... ",
"...... #### #### .......... ## ## .... ",
"...... #### #### .......... .... ",
" .... #### #### .......... #### #### .... ",
"...... #### #### .......... .... ",
"...... #### #### .......... ## ## .... ",
"...... #### #### .......... #### #### .... ",
"...... #### #### .......... ###### ###### .... ",
"...... #### #### .......... ######## ######## .... ",
"...... #### #### .......... ########## ########## .... ",
".. .. ",
".. ################################ .. ################################ ",
".. ################################ ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ################################ ..",
".. ################################ ################################ ..",
".. ################################ ################################ ..",
".. ################################ ################################ ..",
".. ################################ ################################ ..",
".. ################################ ################################ ..",
".. ################################ ################################ ..",
".. ################################ ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ## ################################ ..",
".. ################################ ################################ ..",
".. ################################ .. ################################ ..",
".. .. ..",
".................................. .................................."};
static const byte wage_border_palette[] = {
0, 0, 0,
255, 255, 255,
255, 0, 255
};
void Gui::loadBorders() {
_consoleWindow->enableScrollbar(true);
loadBorder(_sceneWindow, wage_border_inact_title, ARRAYSIZE(wage_border_inact_title), Graphics::kWindowBorderTitle, 22);
loadBorder(_sceneWindow, wage_border_act_noscrollbar_title, ARRAYSIZE(wage_border_act_noscrollbar_title), Graphics::kWindowBorderActive|Graphics::kWindowBorderTitle, 22);
loadBorder(_consoleWindow, wage_border_inact, ARRAYSIZE(wage_border_inact), Graphics::kWindowBorderScrollbar, 0);
loadBorder(_consoleWindow, wage_border_act, ARRAYSIZE(wage_border_act), Graphics::kWindowBorderScrollbar|Graphics::kWindowBorderActive, 0);
}
void Gui::loadBorder(Graphics::MacWindow *target, const char *border[], uint height, uint32 flags, int titlePos) {
uint width = strlen(border[0]) / 2;
Graphics::ManagedSurface *surface = new Graphics::ManagedSurface();
surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
surface->setPalette(wage_border_palette, 0, 3);
surface->setTransparentColor(2);
for (uint y = 0; y < height; y++) {
byte *dst = (byte *)surface->getBasePtr(0, y);
for (uint x = 0; x < width; x++) {
switch(border[y][x * 2]) {
case ' ':
*dst = 0;
break;
case '#':
*dst = 1;
break;
case '.':
*dst = 2;
break;
default:
error("Incorrect symbol in bitmap '%c'(%02x) at %d,%d", border[y][x * 2], border[y][x * 2], x, y);
}
dst++;
}
}
Graphics::BorderOffsets offsets;
offsets.left = 16;
offsets.right = 16;
offsets.top = 16;
offsets.bottom = 16;
offsets.titleTop = 0;
offsets.titleBottom = 0;
offsets.dark = false;
offsets.closeButtonTop = -1;
offsets.closeButtonLeft = -1;
offsets.closeButtonWidth = 0;
offsets.resizeButtonTop = -1;
offsets.resizeButtonHeight = 0;
offsets.upperScrollHeight = 16;
offsets.lowerScrollHeight = 16;
offsets.titlePos = titlePos;
offsets.titlePadding = 8;
target->setBorder(surface, flags, offsets);
}
} // End of namespace Wage

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

119
engines/wage/metaengine.cpp Normal file
View File

@@ -0,0 +1,119 @@
/* 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 "base/plugins.h"
#include "common/system.h"
#include "common/savefile.h"
#include "common/translation.h"
#include "engines/advancedDetector.h"
#include "wage/wage.h"
#include "wage/detection.h"
namespace Wage {
uint32 WageEngine::getFeatures() {
return _gameDescription->flags;
}
const char *WageEngine::getGameFile() const {
return _gameDescription->filesDescriptions[0].fileName;
}
} // End of namespace Wage
#ifdef USE_TTS
static const ADExtraGuiOptionsMap optionsList[] = {
{
GAMEOPTION_TTS,
{
_s("Enable Text to Speech"),
_s("Use TTS to read the descriptions (if TTS is available)"),
"tts_enabled",
false,
0,
0
}
},
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
#endif
class WageMetaEngine : public AdvancedMetaEngine<ADGameDescription> {
public:
const char *getName() const override {
return "wage";
}
#ifdef USE_TTS
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
return optionsList;
}
#endif
Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override;
bool hasFeature(MetaEngineFeature f) const override;
int getMaximumSaveSlot() const override;
};
bool WageMetaEngine::hasFeature(MetaEngineFeature f) const {
return checkExtendedSaves(f) ||
(f == kSupportsLoadingDuringStartup);
}
bool Wage::WageEngine::hasFeature(EngineFeature f) const {
return
(f == kSupportsReturnToLauncher) ||
(f == kSupportsLoadingDuringRuntime) ||
(f == kSupportsSavingDuringRuntime) ||
(f == kSupportsQuitDialogOverride);
}
Common::Error WageMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
*engine = new Wage::WageEngine(syst, desc);
return Common::kNoError;
}
int WageMetaEngine::getMaximumSaveSlot() const { return 999; }
#if PLUGIN_ENABLED_DYNAMIC(WAGE)
REGISTER_PLUGIN_DYNAMIC(WAGE, PLUGIN_TYPE_ENGINE, WageMetaEngine);
#else
REGISTER_PLUGIN_STATIC(WAGE, PLUGIN_TYPE_ENGINE, WageMetaEngine);
#endif
namespace Wage {
bool WageEngine::canLoadGameStateCurrently(Common::U32String *msg) {
return true;
}
bool WageEngine::canSaveGameStateCurrently(Common::U32String *msg) {
return true;
}
} // End of namespace Wage

36
engines/wage/module.mk Normal file
View File

@@ -0,0 +1,36 @@
MODULE := engines/wage
MODULE_OBJS := \
combat.o \
debugger.o \
design.o \
entities.o \
gui.o \
guiborders.o \
metaengine.o \
randomhat.o \
saveload.o \
script.o \
sound.o \
util.o \
wage.o \
world.o
ifdef USE_IMGUI
MODULE_OBJS += \
debugtools.o
endif
MODULE_DIRS += \
engines/wage
# This module can be built as a plugin
ifeq ($(ENABLE_WAGE), DYNAMIC_PLUGIN)
PLUGIN := 1
endif
# Include common rules
include $(srcdir)/rules.mk
# Detection objects
DETECT_OBJS += $(MODULE)/detection.o

View File

@@ -0,0 +1,82 @@
/* 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/>.
*
* MIT License:
*
* Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#include "common/random.h"
#include "common/hashmap.h"
#include "wage/randomhat.h"
namespace Wage {
void RandomHat::addTokens(int type, int count) {
_tokens.setVal(type, _tokens.getValOrDefault(type, 0) + count);
}
int RandomHat::countTokens() {
int count = 0;
for (Common::HashMap<int, int>::const_iterator it = _tokens.begin(); it != _tokens.end(); ++it)
count += it->_value;
return count;
}
int RandomHat::drawToken() {
int total = countTokens();
if (total > 0) {
int random = _rnd->getRandomNumber(total - 1);
int count = 0;
for (Common::HashMap<int, int>::iterator it = _tokens.begin(); it != _tokens.end(); ++it) {
if (random >= count && random < count + it->_value) {
it->_value--;
return it->_key;
}
count += it->_value;
}
}
return kTokNone;
}
} // End of namespace Wage

76
engines/wage/randomhat.h Normal file
View File

@@ -0,0 +1,76 @@
/* 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/>.
*
* MIT License:
*
* Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#ifndef WAGE_RANDOMHAT_H
#define WAGE_RANDOMHAT_H
namespace Wage {
enum {
kTokWeapons = -400,
kTokMagic = -300,
kTokRun = -200,
kTokOffer = -100,
kTokNone = -100000
};
class RandomHat {
public:
RandomHat(Common::RandomSource *rnd) : _rnd(rnd) {}
void addTokens(int type, int count);
int drawToken();
private:
Common::RandomSource *_rnd;
Common::HashMap<int, int> _tokens;
int countTokens();
};
} // End of namespace Wage
#endif

781
engines/wage/saveload.cpp Normal file
View File

@@ -0,0 +1,781 @@
/* 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/>.
*
* MIT License:
*
* Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#include "common/file.h"
#include "common/debug.h"
#include "common/debug-channels.h"
#include "common/config-manager.h"
#include "common/savefile.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/translation.h"
#include "gui/saveload.h"
#include "graphics/thumbnail.h"
#include "graphics/surface.h"
#include "wage/wage.h"
#include "wage/world.h"
#include "wage/entities.h"
#include "wage/gui.h"
#define SAVEGAME_CURRENT_VERSION 1
//
// Original saves format is supported.
// ScummVM adds flags, description and thumbnail
// in the end of the file (shouldn't make saves incompatible).
//
// Version 0 (original/ScummVM): first ScummVM version
//
namespace Wage {
//TODO: make sure these are calculated right: (we add flag, description, etc)
#define VARS_INDEX 0x005E
#define SCENES_INDEX 0x0232
#define SCENE_SIZE 0x0010
#define CHR_SIZE 0x0016
#define OBJ_SIZE 0x0010
#define GET_HEX_OFFSET(ptr, baseOffset, entrySize) ((ptr) == nullptr ? -1 : ((baseOffset) + (entrySize) * (ptr)->_index))
#define GET_HEX_CHR_OFFSET(ptr) GET_HEX_OFFSET((ptr), chrsHexOffset, CHR_SIZE)
#define GET_HEX_OBJ_OFFSET(ptr) GET_HEX_OFFSET((ptr), objsHexOffset, OBJ_SIZE)
#define GET_HEX_SCENE_OFFSET(ptr) ((ptr) == nullptr ? -1 : \
((ptr) == _world->_storageScene ? 0 : (SCENES_INDEX + getSceneIndex(_world->_player->_currentScene) * SCENE_SIZE)))
int WageEngine::getSceneIndex(Scene *scene) const {
assert(scene);
Common::Array<Scene *> &orderedScenes = _world->_orderedScenes;
for (int32 i = 0; i < (int)orderedScenes.size(); ++i) {
if (orderedScenes[i] == scene)
return i - 1;
}
warning("Scene's index not found");
return -1;
}
Obj *WageEngine::getObjByOffset(int offset, int objBaseOffset) const {
int objIndex = -1;
if (offset != 0xFFFF) {
objIndex = (offset - objBaseOffset) / CHR_SIZE;
}
if (objIndex >= 0 && (uint)objIndex < _world->_orderedObjs.size()) {
return _world->_orderedObjs[objIndex];
}
return nullptr;
}
Chr *WageEngine::getChrById(int resId) const {
Common::Array<Chr *> &orderedChrs = _world->_orderedChrs;
for (uint32 i = 0; i < orderedChrs.size(); ++i) {
if (orderedChrs[i]->_resourceId == resId)
return orderedChrs[i];
}
return nullptr;
}
Chr *WageEngine::getChrByOffset(int offset, int chrBaseOffset) const {
int chrIndex = -1;
if (offset != 0xFFFF) {
chrIndex = (offset - chrBaseOffset) / CHR_SIZE;
}
if (chrIndex >= 0 && (uint)chrIndex < _world->_orderedChrs.size()) {
return _world->_orderedChrs[chrIndex];
}
return nullptr;
}
Scene *WageEngine::getSceneById(int resId) const {
Common::Array<Scene *> &orderedScenes = _world->_orderedScenes;
for (uint32 i = 0; i < orderedScenes.size(); ++i) {
if (orderedScenes[i]->_resourceId == resId)
return orderedScenes[i];
}
return nullptr;
}
Scene *WageEngine::getSceneByOffset(int offset) const {
int sceneIndex = -1;
if (offset != 0xFFFF) {
if (offset == 0)
sceneIndex = 0;
else
sceneIndex = 1 + (offset - SCENES_INDEX) / SCENE_SIZE;
}
if (sceneIndex >= 0 && (uint)sceneIndex < _world->_orderedScenes.size()) {
if (sceneIndex == 0) return _world->_storageScene;
return _world->_orderedScenes[sceneIndex];
}
return nullptr;
}
int WageEngine::saveGame(const Common::String &fileName, const Common::String &descriptionString) {
Common::OutSaveFile *out;
int result = 0;
debug(9, "WageEngine::saveGame(%s, %s)", fileName.c_str(), descriptionString.c_str());
if (!(out = _saveFileMan->openForSaving(fileName))) {
warning("Can't create file '%s', game not saved", fileName.c_str());
return -1;
} else {
debug(9, "Successfully opened %s for writing", fileName.c_str());
}
// Counters
out->writeSint16LE(_world->_scenes.size()); //numScenes
out->writeSint16LE(_world->_chrs.size()); //numChars
out->writeSint16LE(_world->_objs.size()); //numObjs
// Hex Offsets
int chrsHexOffset = SCENES_INDEX + _world->_scenes.size() * SCENE_SIZE; //chrs start after scenes
int objsHexOffset = chrsHexOffset + _world->_chrs.size() * CHR_SIZE; //objs start after chrs
out->writeSint32LE(chrsHexOffset);
out->writeSint32LE(objsHexOffset);
// Unique 8-byte World Signature
out->writeSint32LE(_world->_signature); //8-byte ints? seriously? (uses 4 bytes in java code too)
Chr *player = _world->_player;
Context &playerContext = player->_context;
// More Counters
out->writeSint32LE(playerContext._visits); //visitNum
out->writeSint32LE(_loopCount); //loopNum
out->writeSint32LE(playerContext._kills); //killNum
// Hex offset to player character
out->writeSint32LE(GET_HEX_CHR_OFFSET(player)); //getPlayerHexOffset() == getHexOffsetForChr(player)
// character in this scene?
out->writeSint32LE(GET_HEX_CHR_OFFSET(_monster)); //getPresCharHexOffset() == getHexOffsetForChr(monster)
// Hex offset to current scene
out->writeSint32LE(GET_HEX_SCENE_OFFSET(player->_currentScene)); //getCurSceneHexOffset()
// wearing a helmet?
out->writeSint32LE(GET_HEX_OBJ_OFFSET(player->_armor[Chr::HEAD_ARMOR])); //helmetIndex
// holding a shield?
out->writeSint32LE(GET_HEX_OBJ_OFFSET(player->_armor[Chr::SHIELD_ARMOR])); //shieldIndex
// wearing chest armor?
out->writeSint32LE(GET_HEX_OBJ_OFFSET(player->_armor[Chr::BODY_ARMOR])); //chestArmIndex
// wearing spiritual armor?
out->writeSint32LE(GET_HEX_OBJ_OFFSET(player->_armor[Chr::MAGIC_ARMOR])); //sprtArmIndex
// TODO:
out->writeUint16LE(0xffff); // ???? - always FFFF
out->writeUint16LE(0xffff); // ???? - always FFFF
out->writeUint16LE(0xffff); // ???? - always FFFF
out->writeUint16LE(0xffff); // ???? - always FFFF
// did a character just escape?
out->writeSint32LE(GET_HEX_CHR_OFFSET(_running)); //getRunCharHexOffset() == getHexOffsetForChr(running)
// players experience points
out->writeSint32LE(playerContext._experience);
out->writeSint16LE(_aim); //aim
out->writeSint16LE(_opponentAim); //opponentAim
// TODO:
out->writeSint16LE(0x0000); // always 0
out->writeSint16LE(0x0000); // always 0
out->writeSint16LE(0x0000); // always 0
// Base character stats
out->writeByte(playerContext._statVariables[PHYS_STR_BAS]); //state.getBasePhysStr()
out->writeByte(playerContext._statVariables[PHYS_HIT_BAS]); //state.getBasePhysHp()
out->writeByte(playerContext._statVariables[PHYS_ARM_BAS]); //state.getBasePhysArm()
out->writeByte(playerContext._statVariables[PHYS_ACC_BAS]); //state.getBasePhysAcc()
out->writeByte(playerContext._statVariables[SPIR_STR_BAS]); //state.getBaseSprtStr()
out->writeByte(playerContext._statVariables[SPIR_HIT_BAS]); //state.getBaseSprtHp()
out->writeByte(playerContext._statVariables[SPIR_ARM_BAS]); //state.getBaseSprtArm()
out->writeByte(playerContext._statVariables[SPIR_ACC_BAS]); //state.getBaseSprtAcc()
out->writeByte(playerContext._statVariables[PHYS_SPE_BAS]); //state.getBaseRunSpeed()
// TODO:
out->writeByte(0x0A); // ???? - always seems to be 0x0A
// write user vars
for (uint32 i = 0; i < 26 * 9; ++i)
out->writeSint16LE(playerContext._userVariables[i]);
// write updated info for all scenes
Common::Array<Scene *> &orderedScenes = _world->_orderedScenes;
for (uint32 i = 0; i < orderedScenes.size(); ++i) {
Scene *scene = orderedScenes[i];
if (scene != _world->_storageScene) {
out->writeSint16LE(scene->_resourceId);
out->writeSint16LE(scene->_worldY);
out->writeSint16LE(scene->_worldX);
out->writeByte(scene->_blocked[NORTH] ? 0x01 : 0x00);
out->writeByte(scene->_blocked[SOUTH] ? 0x01 : 0x00);
out->writeByte(scene->_blocked[EAST] ? 0x01 : 0x00);
out->writeByte(scene->_blocked[WEST] ? 0x01 : 0x00);
out->writeSint16LE(scene->_soundFrequency);
out->writeByte(scene->_soundType);
// the following two bytes are currently unknown
out->writeByte(0);
out->writeByte(0);
out->writeByte(scene->_visited ? 0x01 : 0x00);
}
}
// write updated info for all characters
Common::Array<Chr *> &orderedChrs = _world->_orderedChrs;
for (uint32 i = 0; i < orderedChrs.size(); ++i) {
Chr *chr = orderedChrs[i];
out->writeSint16LE(chr->_resourceId);
out->writeSint16LE(chr->_currentScene->_resourceId);
Context &chrContext = chr->_context;
out->writeByte(chrContext._statVariables[PHYS_STR_CUR]);
out->writeByte(chrContext._statVariables[PHYS_HIT_CUR]);
out->writeByte(chrContext._statVariables[PHYS_ARM_CUR]);
out->writeByte(chrContext._statVariables[PHYS_ACC_CUR]);
out->writeByte(chrContext._statVariables[SPIR_STR_CUR]);
out->writeByte(chrContext._statVariables[SPIR_HIT_CUR]);
out->writeByte(chrContext._statVariables[SPIR_ARM_CUR]);
out->writeByte(chrContext._statVariables[SPIR_ACC_CUR]);
out->writeByte(chrContext._statVariables[PHYS_SPE_CUR]);
out->writeByte(chr->_rejectsOffers);
out->writeByte(chr->_followsOpponent);
// bytes 16-20 are unknown
out->writeByte(0);
out->writeByte(0);
out->writeByte(0);
out->writeByte(0);
out->writeByte(0);
out->writeByte(chr->_weaponDamage1);
out->writeByte(chr->_weaponDamage2);
}
// write updated info for all objects
Common::Array<Obj *> &orderedObjs = _world->_orderedObjs;
for (uint32 i = 0; i < orderedObjs.size(); ++i) {
Obj *obj = orderedObjs[i];
Scene *location = obj->_currentScene;
Chr *owner = obj->_currentOwner;
out->writeSint16LE(obj->_resourceId);
out->writeSint16LE(location == nullptr ? 0 : location->_resourceId);
out->writeSint16LE(owner == nullptr ? 0 : owner->_resourceId);
// bytes 7-9 are unknown (always = 0)
out->writeByte(0);
out->writeByte(0);
out->writeByte(0);
out->writeByte(obj->_accuracy);
out->writeByte(obj->_value);
out->writeByte(obj->_type);
out->writeByte(obj->_damage);
out->writeByte(obj->_attackType);
out->writeSint16LE(obj->_numberOfUses);
}
// the following is appended by ScummVM
g_engine->getMetaEngine()->appendExtendedSave(out, g_engine->getTotalPlayTime(), descriptionString, fileName.contains("auto"));
out->finalize();
if (out->err()) {
warning("Can't write file '%s'. (Disk full?)", fileName.c_str());
result = -1;
} else
debug(9, "Saved game %s in file %s", descriptionString.c_str(), fileName.c_str());
delete out;
return result;
}
int WageEngine::loadGame(int slotId) {
Common::InSaveFile *data;
Common::String fileName = getSaveStateName(slotId);
debug(9, "WageEngine::loadGame(%d)", slotId);
if (!(data = _saveFileMan->openForLoading(fileName))) {
warning("Can't open file '%s', game not loaded", fileName.c_str());
return -1;
} else {
debug(9, "Successfully opened %s for reading", fileName.c_str());
}
// Counters
int numScenes = data->readSint16LE();
int numChars = data->readSint16LE();
int numObjs = data->readSint16LE();
// Hex Offsets
int chrsHexOffset = data->readSint32LE();
int objsHexOffset = data->readSint32LE();
// Unique 8-byte World Signature
int signature = data->readSint32LE();
if (_world->_signature != signature) {
warning("This saved game is for a different world, please select another one");
warning("World signature = %d, save signature = %d", _world->_signature, signature);
delete data;
return -1;
}
// More Counters
int visitNum = data->readSint32LE(); //visitNum
int loopNum = data->readSint32LE(); //loopNum
int killNum = data->readSint32LE(); //killNum
// Hex offset to player character
int playerOffset = data->readSint32LE();
// character in this scene?
int presCharOffset = data->readSint32LE();
// Hex offset to current scene
int currentSceneOffset = data->readSint32LE();
// find player and current scene
Chr *player = getChrByOffset(playerOffset, chrsHexOffset);
if (player == nullptr) {
warning("Invalid Character! Aborting load.");
delete data;
return -1;
}
Scene *currentScene = getSceneByOffset(currentSceneOffset);
if (currentScene == nullptr) {
warning("Invalid Scene! Aborting load.");
delete data;
return -1;
}
// set player character
_world->_player = player;
// set current scene
player->_currentScene = currentScene;
// clear the players inventory list
player->_inventory.clear();
// wearing a helmet?
int helmetOffset = data->readSint32LE(); //helmetIndex
// holding a shield?
int shieldOffset = data->readSint32LE(); //shieldIndex
// wearing chest armor?
int armorOffset = data->readSint32LE(); //chestArmIndex
// wearing spiritual armor?
int spiritualArmorOffset = data->readSint32LE(); //sprtArmIndex
data->readSint16LE(); // FFFF
data->readSint16LE(); // FFFF
data->readSint16LE(); // FFFF
data->readSint16LE(); // FFFF
/* int runCharOffset = */ data->readSint32LE();
// players experience points
int exp = data->readSint32LE(); // @ playerContext._experience
int aim = data->readSint16LE(); //aim
int opponentAim = data->readSint16LE(); //opponentAim
data->readSint16LE(); // 0000
data->readSint16LE(); // 0000
data->readSint16LE(); // 0000
// Base character stats
int basePhysStr = data->readByte();
int basePhysHp = data->readByte();
int basePhysArm = data->readByte();
int basePhysAcc = data->readByte();
int baseSprtStr = data->readByte();
int baseSprtHp = data->readByte();
int baseSprtArm = data->readByte();
int baseSprtAcc = data->readByte();
int baseRunSpeed = data->readByte();
// set player stats
Context &playerContext = player->_context;
// I'm setting player fields also, because those are used as base values in Chr::resetState()
playerContext._statVariables[PHYS_STR_BAS] = player->_physicalStrength = basePhysStr;
playerContext._statVariables[PHYS_HIT_BAS] = player->_physicalHp = basePhysHp;
playerContext._statVariables[PHYS_ARM_BAS] = player->_naturalArmor = basePhysArm;
playerContext._statVariables[PHYS_ACC_BAS] = player->_physicalAccuracy = basePhysAcc;
playerContext._statVariables[SPIR_STR_BAS] = player->_spiritualStength = baseSprtStr;
playerContext._statVariables[SPIR_HIT_BAS] = player->_spiritialHp = baseSprtHp;
playerContext._statVariables[SPIR_ARM_BAS] = player->_resistanceToMagic = baseSprtArm;
playerContext._statVariables[SPIR_ACC_BAS] = player->_spiritualAccuracy = baseSprtAcc;
playerContext._statVariables[PHYS_SPE_BAS] = player->_runningSpeed = baseRunSpeed;
// set visit#
playerContext._visits = visitNum;
// set monsters killed
playerContext._kills = killNum;
// set experience
playerContext._experience = exp;
// if a character is present, move it to this scene
// TODO: This is done in the engine object, would it be cleaner
// to move it here?
// well, it's actually down there now, now sure if that's "cleaner"
// when it's up there or down there
// if a character just ran away, let our engine know
// TODO: The current engine doesn't have a case for this, we
// should update it
// yep, I don't see such code anywhere in java, so not added it here
data->readByte(); // 0x0A?
// set all user variables
for (uint32 i = 0; i < 26 * 9; ++i) {
playerContext._userVariables[i] = data->readSint16LE();
}
// update all scene stats
Common::Array<Scene *> &orderedScenes = _world->_orderedScenes;
if ((uint)numScenes != orderedScenes.size()) {
warning("scenes number in file (%d) differs from the one in world (%d)", numScenes, orderedScenes.size());
}
for (uint32 i = 0; i < orderedScenes.size(); ++i) {
Scene *scene = orderedScenes[i];
if (scene != _world->_storageScene) {
int id = data->readSint16LE();
if (scene->_resourceId != id) {
warning("loadGame(): updating scenes: expected %d but got %d", scene->_resourceId, id);
data->skip(14); //2,2,1,1,1,1,2,1,1,1,1 down there
continue;
}
scene->_worldY = data->readSint16LE();
scene->_worldX = data->readSint16LE();
scene->_blocked[NORTH] = data->readByte() != 0;
scene->_blocked[SOUTH] = data->readByte() != 0;
scene->_blocked[EAST] = data->readByte() != 0;
scene->_blocked[WEST] = data->readByte() != 0;
scene->_soundFrequency = data->readSint16LE();
scene->_soundType = data->readByte();
// the following two bytes are currently unknown
data->readByte();
data->readByte();
scene->_visited = data->readByte() != 0;
}
scene->_chrs.clear();
scene->_objs.clear();
}
// update all char locations and stats
Common::Array<Chr *> &orderedChrs = _world->_orderedChrs;
if ((uint)numChars != orderedChrs.size()) {
warning("characters number in file (%d) differs from the one in world (%d)", numChars, orderedChrs.size());
}
for (uint32 i = 0; i < orderedChrs.size(); ++i) {
int resourceId = data->readSint16LE();
int sceneResourceId = data->readSint16LE();
int strength = data->readByte();
int hp = data->readByte();
int armor = data->readByte();
int accuracy = data->readByte();
int spirStrength = data->readByte();
int spirHp = data->readByte();
int spirArmor = data->readByte();
int spirAccuracy = data->readByte();
int speed = data->readByte();
int rejectsOffers = data->readByte();
int followsOpponent = data->readByte();
// bytes 16-20 are unknown
data->readByte();
data->readByte();
data->readByte();
data->readByte();
data->readByte();
int weaponDamage1 = data->readByte();
int weaponDamage2 = data->readByte();
Chr *chr = orderedChrs[i];
if (chr->_resourceId != resourceId) {
warning("loadGame(): updating chrs: expected %d but got %d", chr->_resourceId, resourceId);
continue;
}
chr->_currentScene = getSceneById(sceneResourceId);
Context &chrContext = chr->_context;
chrContext._statVariables[PHYS_STR_CUR] = strength;
chrContext._statVariables[PHYS_HIT_CUR] = hp;
chrContext._statVariables[PHYS_ARM_CUR] = armor;
chrContext._statVariables[PHYS_ACC_CUR] = accuracy;
chrContext._statVariables[SPIR_STR_CUR] = spirStrength;
chrContext._statVariables[SPIR_HIT_CUR] = spirHp;
chrContext._statVariables[SPIR_ARM_CUR] = spirArmor;
chrContext._statVariables[SPIR_ACC_CUR] = spirAccuracy;
chrContext._statVariables[PHYS_SPE_CUR] = speed;
chr->_rejectsOffers = rejectsOffers;
chr->_followsOpponent = followsOpponent;
chr->_weaponDamage1 = weaponDamage1;
chr->_weaponDamage2 = weaponDamage2;
}
// update all object locations and stats
Common::Array<Obj *> &orderedObjs = _world->_orderedObjs;
if ((uint)numObjs != orderedObjs.size()) {
warning("objects number in file (%d) differs from the one in world (%d)", numObjs, orderedObjs.size());
}
for (uint32 i = 0; i < orderedObjs.size(); ++i) {
int resourceId = data->readSint16LE();
int locationResourceId = data->readSint16LE();
int ownerResourceId = data->readSint16LE();
// bytes 7-9 are unknown (always = 0)
data->readByte();
data->readByte();
data->readByte();
int accuracy = data->readByte();
int value = data->readByte();
int type = data->readByte();
int damage = data->readByte();
int attackType= data->readByte();
int numberOfUses = data->readSint16LE();
Obj *obj = orderedObjs[i];
if (obj->_resourceId != resourceId) {
warning("loadGame(): updating objs: expected %d but got %d", obj->_resourceId, resourceId);
continue;
}
if (ownerResourceId != 0) {
obj->setCurrentOwner(getChrById(ownerResourceId));
if (obj->_currentOwner == nullptr)
warning("loadGame(): updating objs: owner not found - char with id %d", ownerResourceId);
} else {
obj->setCurrentScene(getSceneById(locationResourceId));
if (obj->_currentScene == nullptr)
warning("loadGame(): updating objs: scene with id %d not found", ownerResourceId);
}
obj->_accuracy = accuracy;
obj->_value = value;
obj->_type = type;
obj->_damage = damage;
obj->_attackType = attackType;
obj->_numberOfUses = numberOfUses;
}
// update inventories and scene contents
for (uint32 i = 0; i < orderedObjs.size(); ++i) {
Obj *obj = orderedObjs[i];
Chr *chr = obj->_currentOwner;
if (chr != nullptr) {
chr->_inventory.push_back(obj);
} else {
Scene *scene = obj->_currentScene;
scene->_objs.push_back(obj);
}
}
// update scene chrs
for (uint32 i = 0; i < orderedChrs.size(); ++i) {
Chr *chr = orderedChrs[i];
Scene *scene = chr->_currentScene;
scene->_chrs.push_back(chr);
if (chr != player) {
wearObjs(chr);
}
}
// move all worn helmets, shields, chest armors and spiritual
// armors to player
for (int type = 0; type < Chr::NUMBER_OF_ARMOR_TYPES; ++type) {
Obj *armor;
if (type == Chr::HEAD_ARMOR)
armor = getObjByOffset(helmetOffset, objsHexOffset);
else if (type == Chr::SHIELD_ARMOR)
armor = getObjByOffset(shieldOffset, objsHexOffset);
else if (type == Chr::BODY_ARMOR)
armor = getObjByOffset(armorOffset, objsHexOffset);
else
armor = getObjByOffset(spiritualArmorOffset, objsHexOffset);
if (armor != nullptr) {
_world->move(armor, player);
player->_armor[type] = armor;
}
}
//TODO: make sure that armor in the inventory gets put on if we are wearing it
_loopCount = loopNum;
// let the engine know if there is a npc in the current scene
if (presCharOffset != 0xffff) {
_monster = getChrByOffset(presCharOffset, chrsHexOffset);
}
// java engine calls clearOutput(); here
// processTurn("look", NULL); called in Wage right after this loadGame()
// TODO: as you may see, aim, opponentAim or runCharOffset are not used anywhere
// I'm fixing the first two, as those are clearly not even mentioned anywhere
// the runCharOffset is mentioned up there as "not implemented case"
_aim = aim;
_opponentAim = opponentAim;
// Load the savaegame header
ExtendedSavegameHeader header;
if (!MetaEngine::readSavegameHeader(data, &header, true))
error("Invalid savegame");
_defaultSaveDescritpion = header.description;
delete data;
return 0;
}
Common::Error WageEngine::loadGameState(int slot) {
warning("LOADING %d", slot);
if (_isGameOver)
resetState();
_gui->_consoleWindow->clearText();
if (loadGame(slot) == 0) {
if (slot != getAutosaveSlot()) {
_defaultSaveSlot = slot;
// save description is set inside of loadGame()
_gui->enableSave();
}
sayText(_world->_player->_currentScene->_name, Common::TextToSpeechManager::QUEUE);
_gui->regenCommandsMenu();
_gui->regenWeaponsMenu();
_gui->_consoleWindow->setTextWindowFont(_world->_player->_currentScene->getFont());
Common::String input("look");
processTurn(&input, NULL);
return Common::kNoError;
} else {
return Common::kUnknownError;
}
}
Common::Error WageEngine::saveGameState(int slot, const Common::String &description, bool isAutosave) {
Common::String saveLoadSlot = getSaveStateName(slot);
if (saveGame(saveLoadSlot, description) == 0) {
if (slot != getAutosaveSlot()) {
_defaultSaveSlot = slot;
_defaultSaveDescritpion = description;
_gui->enableSave();
}
return Common::kNoError;
} else {
return Common::kUnknownError;
}
}
bool WageEngine::scummVMSaveLoadDialog(bool isSave) {
if (!isSave) {
// do loading
GUI::SaveLoadChooser dialog = GUI::SaveLoadChooser(_("Load game:"), _("Load"), false);
int slot = dialog.runModalWithCurrentTarget();
if (slot < 0)
return true;
return loadGameState(slot).getCode() == Common::kNoError;
}
// do saving
GUI::SaveLoadChooser dialog = GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
int slot = dialog.runModalWithCurrentTarget();
Common::String desc = dialog.getResultString();
if (desc.empty()) {
// create our own description for the saved game, the user didn't enter it
desc = dialog.createDefaultSaveDescription(slot);
}
if (desc.size() > 28)
desc = Common::String(desc.c_str(), 28);
if (slot < 0)
return true;
return saveGameState(slot, desc).getCode() == Common::kNoError;
}
} // End of namespace Agi

1353
engines/wage/script.cpp Normal file

File diff suppressed because it is too large Load Diff

161
engines/wage/script.h Normal file
View File

@@ -0,0 +1,161 @@
/* 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/>.
*
* MIT License:
*
* Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#ifndef WAGE_SCRIPT_H
#define WAGE_SCRIPT_H
namespace Wage {
class Script {
public:
Script(Common::SeekableReadStream *data, int num, WageEngine *engine);
~Script();
private:
Common::SeekableReadStream *_data;
WageEngine *_engine;
World *_world;
int _loopCount;
Common::String *_inputText;
Designed *_inputClick;
bool _handled;
class Operand {
public:
union {
Obj *obj;
Chr *chr;
Designed *designed;
Scene *scene;
int16 number;
Common::String *string;
Designed *inputClick;
} _value;
OperandType _type;
Operand(Obj *value, OperandType type) {
_value.obj = value;
assert(type == OBJ);
_type = type;
}
Operand(Chr *value, OperandType type) {
_value.chr = value;
assert(type == CHR);
_type = type;
}
Operand(Scene *value, OperandType type) {
_value.scene = value;
assert(type == SCENE);
_type = type;
}
Operand(int value, OperandType type) {
_value.number = value;
assert(type == NUMBER);
_type = type;
}
Operand(Common::String *value, OperandType type) {
_value.string = value;
assert(type == STRING || type == TEXT_INPUT);
_type = type;
}
Operand(Designed *value, OperandType type) {
_value.inputClick = value;
assert(type == CLICK_INPUT);
_type = type;
}
~Operand() {
if (_type == STRING)
delete _value.string;
}
Common::String toString() const;
};
struct ScriptText {
int offset;
Common::String line;
};
public:
void print();
void printLine(int offset);
bool execute(World *world, int loopCount, Common::String *inputText, Designed *inputClick);
private:
Common::String preprocessInputText(Common::String inputText);
Operand *readOperand();
Operand *readStringOperand();
const char *readOperator();
void processIf();
void skipBlock();
void skipIf();
bool compare(Operand *o1, Operand *o2, int comparator);
bool eval(Operand *lhs, const char *op, Operand *rhs);
bool evaluatePair(Operand *lhs, const char *op, Operand *rhs);
Operand *convertOperand(Operand *operand, int type);
bool evalClickCondition(Operand *lhs, const char *op, Operand *rhs);
bool evalClickEquality(Operand *lhs, Operand *rhs, bool partialMatch);
void processMove();
void processLet();
void assign(byte operandType, int uservar, uint16 value);
void convertToText();
public:
Common::Array<ScriptText *> _scriptText;
};
} // End of namespace Wage
#endif

212
engines/wage/sound.cpp Normal file
View File

@@ -0,0 +1,212 @@
/* 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/>.
*
* MIT License:
*
* Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "audio/decoders/raw.h"
#include "common/config-manager.h"
#include "common/stream.h"
#include "common/system.h"
#include "wage/wage.h"
#include "wage/entities.h"
#include "wage/gui.h"
#include "wage/sound.h"
#include "wage/world.h"
namespace Wage {
static const int8 deltas[] = { 0,-49,-36,-25,-16,-9,-4,-1,0,1,4,9,16,25,36,49 };
Sound::Sound(Common::String name, Common::SeekableReadStream *data) : _name(name) {
_size = data->size() - 20;
_data = (byte *)calloc(2 * _size, 1);
debugC(1, kDebugSound, "Sound::Sound: loading sound '%s', size %d", name.c_str(), _size);
data->skip(20); // Skip header
byte value = 0x80;
for (uint i = 0; i < _size; i++) {
byte d = data->readByte();
value += deltas[d & 0xf];
_data[i * 2] = value;
value += deltas[(d >> 4) & 0xf];
_data[i * 2 + 1] = value;
}
}
Sound::~Sound() {
free(_data);
}
void WageEngine::playSound(Common::String soundName, bool blocking) {
soundName.toLowercase();
if (!_world->_sounds.contains(soundName)) {
if (!soundName.empty())
warning("playSound: Sound '%s' does not exist", soundName.c_str());
return;
}
debugC(1, kDebugSound, "WageEngine::playSound: playing sound '%s'%s", soundName.c_str(), blocking ? " blocking" : "");
Sound *s = _world->_sounds[soundName];
Audio::AudioStream *stream = Audio::makeRawStream(s->_data, 2 * s->_size, 11000, Audio::FLAG_UNSIGNED);
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, stream,
-1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
while (_mixer->isSoundHandleActive(_soundHandle) && !_shouldQuit && blocking) {
Common::Event event;
if (_eventMan->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_QUIT:
if (ConfMan.hasKey("confirm_exit") && ConfMan.getBool("confirm_exit")) {
if (!_shouldQuit) {
g_system->getEventManager()->resetQuit();
if (_gui->saveDialog()) {
_shouldQuit = true;
g_system->getEventManager()->pushEvent(event);
}
}
} else {
_shouldQuit = true;
}
break;
default:
break;
}
}
_system->updateScreen();
_system->delayMillis(10);
}
}
static void soundTimer(void *refCon) {
Scene *scene = (Scene *)refCon;
WageEngine *engine = (WageEngine *)g_engine;
if (!engine)
return;
g_system->getTimerManager()->removeTimerProc(&soundTimer);
if (engine->_world->_player->_currentScene != scene)
return;
if (engine->_soundQueue.empty() && engine->_soundToPlay.empty()) {
if (scene->_soundType == Scene::PERIODIC) {
engine->_soundToPlay = scene->_soundName; // We cannot play sound here because that goes recursively
uint32 nextRun = 60000 / scene->_soundFrequency;
g_system->getTimerManager()->installTimerProc(&soundTimer, nextRun * 1000, scene, "WageEngine::soundTimer");
debugC(1, kDebugSound, "soundTimer: enqueuing next periodic sound in %d ms (%s)", nextRun, scene->_soundName.c_str());
} else if (scene->_soundType == Scene::RANDOM) {
for (int i = 0; i < scene->_soundFrequency; i++) {
uint32 nextRun = g_system->getMillis() + engine->_rnd->getRandomNumber(60000);
engine->_soundQueue.push_back(nextRun);
debugC(1, kDebugSound, "soundTimer: enqueuing next random sound in %d ms (%s)", nextRun, scene->_soundName.c_str());
}
Common::sort(engine->_soundQueue.begin(), engine->_soundQueue.end());
int nextRun = engine->_soundQueue.front() - g_system->getMillis();
engine->_soundQueue.pop_front();
if (nextRun < 0)
nextRun = 1;
g_system->getTimerManager()->installTimerProc(&soundTimer, nextRun * 1000, scene, "WageEngine::soundTimer");
} else {
warning("updateSoundTimerForScene: Unknown sound type %d", scene->_soundType);
}
} else {
// Do not play sound if another is still playing
if (!engine->_soundToPlay.empty()) {
g_system->getTimerManager()->installTimerProc(&soundTimer, g_system->getMillis() + 100, scene, "WageEngine::soundTimer");
return;
}
int nextRun = engine->_soundQueue.front();
engine->_soundQueue.pop_front();
g_system->getTimerManager()->installTimerProc(&soundTimer, (nextRun - g_system->getMillis()) * 1000, scene, "WageEngine::soundTimer");
engine->_soundToPlay = scene->_soundName; // We cannot play sound here because that goes recursively
debugC(1, kDebugSound, "soundTimer: preparing next sound in %d ms (%s)", nextRun - g_system->getMillis(), scene->_soundName.c_str());
}
}
void WageEngine::updateSoundTimerForScene(Scene *scene, bool firstTime) {
if (_world->_player->_currentScene != scene)
return;
if (scene->_soundFrequency > 0 && !scene->_soundName.empty()) {
Common::String soundName(scene->_soundName);
soundName.toLowercase();
if (!_world->_sounds.contains(soundName)) {
warning("updateSoundTimerForScene: Sound '%s' does not exist", soundName.c_str());
return;
}
// Launch the process
soundTimer(scene);
}
}
} // End of namespace Wage

64
engines/wage/sound.h Normal file
View File

@@ -0,0 +1,64 @@
/* 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/>.
*
* MIT License:
*
* Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#ifndef WAGE_SOUND_H
#define WAGE_SOUND_H
namespace Wage {
class Sound {
public:
Sound(Common::String name, Common::SeekableReadStream *data);
~Sound();
Common::String _name;
uint _size;
byte *_data;
};
} // End of namespace Wage
#endif

130
engines/wage/util.cpp Normal file
View File

@@ -0,0 +1,130 @@
/* 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/>.
*
* MIT License:
*
* Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#include "common/stream.h"
#include "wage/wage.h"
namespace Wage {
Common::Rect *readRect(Common::SeekableReadStream *in) {
int x1, y1, x2, y2;
// Account for the extra two pixels because of the squares on the border
y1 = in->readSint16BE() - 2;
x1 = in->readSint16BE() - 2;
y2 = in->readSint16BE() + 2;
x2 = in->readSint16BE() + 2;
bool normalized = false;
if (x1 > x2) {
SWAP(x1, x2);
normalized = true;
}
if (y1 > y2) {
SWAP(y1, y2);
normalized = true;
}
debug(9, "readRect: %s%d, %d, %d, %d", normalized ? "norm " : "", x1, y1, x2, y2);
return new Common::Rect(x1, y1, x2, y2);
}
const char *getIndefiniteArticle(const Common::String &word) {
switch (word[0]) {
case 'a': case 'A':
case 'e': case 'E':
case 'i': case 'I':
case 'o': case 'O':
case 'u': case 'U':
return "an ";
default:
break;
}
return "a ";
}
enum {
GENDER_MALE = 0,
GENDER_FEMALE = 1,
GENDER_NEUTRAL = 2
};
const char *prependGenderSpecificPronoun(int gender) {
if (gender == GENDER_MALE)
return "his ";
else if (gender == GENDER_FEMALE)
return "her ";
else
return "its ";
}
const char *getGenderSpecificPronoun(int gender, bool capitalize) {
if (gender == GENDER_MALE)
return capitalize ? "He" : "he";
else if (gender == GENDER_FEMALE)
return capitalize ? "She" : "she";
else
return capitalize ? "It" : "it";
}
bool isStorageScene(const Common::String &name) {
if (name.equalsIgnoreCase(STORAGESCENE))
return true;
if (name.equalsIgnoreCase("STROAGE@")) // Jumble
return true;
if (name.equalsIgnoreCase("STORAGE@@")) // Jumble
return true;
return false;
}
} // End of namespace Wage

669
engines/wage/wage.cpp Normal file
View File

@@ -0,0 +1,669 @@
/* 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/>.
*
* MIT License:
*
* Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#include "common/config-manager.h"
#include "common/debug-channels.h"
#include "common/error.h"
#include "common/events.h"
#include "common/punycode.h"
#include "common/system.h"
#include "common/text-to-speech.h"
#include "audio/softsynth/pcspk.h"
#include "engines/engine.h"
#include "engines/util.h"
#include "wage/wage.h"
#include "wage/debugtools.h"
#include "wage/entities.h"
#include "wage/gui.h"
#include "wage/script.h"
#include "wage/world.h"
namespace Wage {
WageEngine *g_wage = nullptr;
WageEngine::WageEngine(OSystem *syst, const ADGameDescription *desc) : Engine(syst), _gameDescription(desc) {
_rnd = new Common::RandomSource("wage");
_aim = Chr::SIDE;
_opponentAim = -1;
_temporarilyHidden = false;
_isGameOver = false;
_monster = NULL;
_running = NULL;
_lastScene = NULL;
_loopCount = 0;
_turn = 0;
_commandWasQuick = false;
_shouldQuit = false;
_gui = NULL;
_world = NULL;
_offer = NULL;
_resManager = NULL;
_speaker = NULL;
g_wage = this;
debug("WageEngine::WageEngine()");
}
WageEngine::~WageEngine() {
debug("WageEngine::~WageEngine()");
delete _world;
delete _resManager;
delete _gui;
delete _rnd;
g_engine = nullptr;
g_wage = nullptr;
}
bool WageEngine::pollEvent(Common::Event &event) {
return _eventMan->pollEvent(event);
}
Common::Error WageEngine::run() {
debug("WageEngine::init");
int width = 512;
int height = 342;
if (getFeatures() & GF_RES800) {
width = 800;
height = 600;
} else if (getFeatures() & GF_RES1024) {
width = 1024;
height = 768;
}
initGraphics(width, height);
setDebugger(new Debugger(this));
// Your main event loop should be (invoked from) here.
_resManager = new Common::MacResManager();
if (!_resManager->open(Common::Path(getGameFile()).punycodeDecode()))
error("Could not open %s as a resource fork", getGameFile());
_world = new World(this);
if (!_world->loadWorld(_resManager))
return Common::kNoGameDataFoundError;
_shouldQuit = false;
_gui = new Gui(this);
#ifdef USE_IMGUI
ImGuiCallbacks callbacks;
bool drawImGui = debugChannelSet(-1, kDebugImGui);
callbacks.init = onImGuiInit;
callbacks.render = drawImGui ? onImGuiRender : nullptr;
callbacks.cleanup = onImGuiCleanup;
_system->setImGuiCallbacks(callbacks);
#endif
_speaker = new Audio::PCSpeaker();
_speaker->init();
_temporarilyHidden = true;
performInitialSetup();
if (ConfMan.hasKey("save_slot")) {
int saveSlot = ConfMan.getInt("save_slot");
loadGame(saveSlot);
_gui->regenCommandsMenu();
_gui->regenWeaponsMenu();
}
_gui->_consoleWindow->setTextWindowFont(_world->_player->_currentScene->getFont());
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan) {
ttsMan->setLanguage(ConfMan.get("language"));
ttsMan->enable(ConfMan.getBool("tts_enabled"));
_gui->_wm->setTTSEnabled(ConfMan.getBool("tts_enabled"));
}
Common::String input("look");
processTurn(&input, NULL);
_temporarilyHidden = false;
while (!_shouldQuit) {
processEvents();
if (_restartRequested)
restart();
if (_gui)
_gui->draw();
g_system->updateScreen();
g_system->delayMillis(50);
if (!_soundToPlay.empty() && !_mixer->isSoundHandleActive(_soundHandle)) {
debugC(1, kDebugSound, "** Sound from queue: %s", _soundToPlay.c_str());
playSound(_soundToPlay, false); // Do not block input
_soundToPlay.clear();
}
}
return Common::kNoError;
}
// Resetting required variables
void WageEngine::resetState() {
_aim = Chr::CHEST;
_opponentAim = -1;
_temporarilyHidden = false;
_isGameOver = false;
_monster = nullptr;
_running = nullptr;
_lastScene = nullptr;
_loopCount = 0;
_turn = 0;
_commandWasQuick = false;
_shouldQuit = false;
_offer = nullptr;
delete _speaker;
}
void WageEngine::restart() {
if (_isGameOver)
resetState();
_restartRequested = false;
delete _gui;
delete _world;
_gui = nullptr;
_world = new World(this);
if (!_world->loadWorld(_resManager)) {
_shouldQuit = true;
return;
}
_shouldQuit = false;
_gui = new Gui(this);
_temporarilyHidden = true;
performInitialSetup();
Common::String input("look");
processTurn(&input, NULL);
}
void WageEngine::processEvents() {
Common::Event event;
while (_eventMan->pollEvent(event)) {
if (_gui->processEvent(event))
continue;
switch (event.type) {
case Common::EVENT_QUIT:
case Common::EVENT_RETURN_TO_LAUNCHER:
if (ConfMan.hasKey("confirm_exit") && ConfMan.getBool("confirm_exit")) {
if (!_shouldQuit) {
g_system->getEventManager()->resetQuit();
g_system->getEventManager()->resetReturnToLauncher();
if (_gui->saveDialog()) {
_shouldQuit = true;
g_system->getEventManager()->pushEvent(event);
}
}
} else {
_shouldQuit = true;
}
break;
case Common::EVENT_KEYDOWN:
switch (event.kbd.keycode) {
case Common::KEYCODE_RETURN: {
_inputText = Common::convertFromU32String(_gui->_consoleWindow->getInput());
Common::String inp = _inputText + '\n';
sayText(_gui->_consoleWindow->getInput(), Common::TextToSpeechManager::INTERRUPT);
_gui->appendText(inp.c_str());
_gui->_consoleWindow->clearInput();
if (_inputText.empty())
break;
processTurn(&_inputText, NULL);
_gui->disableUndo();
_gui->enableRevert();
break;
}
default:
break;
}
break;
default:
break;
}
}
}
void WageEngine::setMenu(Common::String menu) {
_world->_commandsMenu = menu;
_gui->regenCommandsMenu();
}
void WageEngine::appendText(const char *str) {
Common::String s(str);
// HACK: Added here because sometimes empty strings would be passed, leading to extra newlines
if (!s.empty()){
s += '\n';
_gui->appendText(s.c_str());
sayText(s, Common::TextToSpeechManager::QUEUE);
}
_inputText.clear();
}
void WageEngine::sayText(const Common::U32String &str, Common::TextToSpeechManager::Action action) const {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan && ConfMan.getBool("tts_enabled")) {
ttsMan->say(str, action);
}
}
void WageEngine::sayText(const Common::String &str, Common::TextToSpeechManager::Action action) const {
sayText(Common::U32String(str, Common::CodePage::kMacRoman), action);
}
void WageEngine::saveGame() {
if (_defaultSaveSlot != -1 && _defaultSaveSlot != getAutosaveSlot())
saveGameState(_defaultSaveSlot, _defaultSaveDescritpion, false);
else
scummVMSaveLoadDialog(true);
}
void WageEngine::performInitialSetup() {
debug(5, "Resetting Objs: %d", _world->_orderedObjs.size());
if (_world->_orderedObjs.size() > 0) {
for (uint i = 0; i < _world->_orderedObjs.size() - 1; i++)
_world->move(_world->_orderedObjs[i], _world->_storageScene, true);
_world->move(_world->_orderedObjs[_world->_orderedObjs.size() - 1], _world->_storageScene);
}
debug(5, "Resetting Chrs: %d", _world->_orderedChrs.size());
if (_world->_orderedChrs.size() > 0) {
for (uint i = 0; i < _world->_orderedChrs.size() - 1; i++)
_world->move(_world->_orderedChrs[i], _world->_storageScene, true);
_world->move(_world->_orderedChrs[_world->_orderedChrs.size() - 1], _world->_storageScene);
}
debug(5, "Resetting Owners: %d", _world->_orderedObjs.size());
for (uint i = 0; i < _world->_orderedObjs.size(); i++) {
Obj *obj = _world->_orderedObjs[i];
if (!isStorageScene(obj->_sceneOrOwner)) {
Common::String location = obj->_sceneOrOwner;
location.toLowercase();
Scene *scene = getSceneByName(location);
if (scene != NULL) {
_world->move(obj, scene);
} else {
if (!_world->_chrs.contains(location)) {
// Note: PLAYER@ is not a valid target here.
warning("Couldn't move %s to \"%s\"", obj->_name.c_str(), obj->_sceneOrOwner.c_str());
} else {
// TODO: Add check for max items.
_world->move(obj, _world->_chrs[location]);
}
}
}
}
bool playerPlaced = false;
for (uint i = 0; i < _world->_orderedChrs.size(); i++) {
Chr *chr = _world->_orderedChrs[i];
if (!isStorageScene(chr->_initialScene)) {
Common::String key = chr->_initialScene;
key.toLowercase();
if (_world->_scenes.contains(key) && _world->_scenes[key] != NULL) {
_world->move(chr, _world->_scenes[key]);
if (chr->_playerCharacter)
debug(0, "Initial scene: %s", key.c_str());
} else {
_world->move(chr, _world->getRandomScene());
}
if (chr->_playerCharacter) {
playerPlaced = true;
}
}
chr->wearObjs();
}
if (!playerPlaced) {
_world->move(_world->_player, _world->getRandomScene());
}
sayText(_world->_player->_currentScene->_name);
// Set the console window's dimensions early here because
// flowText() that needs them gets called before they're set
_gui->_consoleWindow->setDimensions(*_world->_player->_currentScene->_textBounds);
}
void WageEngine::wearObjs(Chr* chr) {
if (chr != nullptr)
chr->wearObjs();
}
void WageEngine::doClose() {
// No op on ScummVM since we do not allow to load arbitrary games
}
Scene *WageEngine::getSceneByName(Common::String &location) {
if (location.equals("random@")) {
return _world->getRandomScene();
} else {
if (_world->_scenes.contains(location))
return _world->_scenes[location];
else
return NULL;
}
}
void WageEngine::onMove(Designed *what, Designed *from, Designed *to) {
Chr *player = _world->_player;
Scene *currentScene = player->_currentScene;
if (currentScene == _world->_storageScene && !_temporarilyHidden) {
if (!_isGameOver) {
_isGameOver = true;
_gui->gameOver();
}
return;
}
if (from == currentScene || to == currentScene ||
(what->_classType == CHR && ((Chr *)what)->_currentScene == currentScene) ||
(what->_classType == OBJ && ((Obj *)what)->_currentScene == currentScene))
_gui->setSceneDirty();
if ((from == player || to == player) && !_temporarilyHidden)
_gui->regenWeaponsMenu();
if (what != player && what->_classType == CHR) {
Chr *chr = (Chr *)what;
// the original code below forced enemies to immediately respawn if moved to storage.
// this broke the "Escape" mechanic, comment it out so they stay in storage.
//if (to == _world->_storageScene) {
// int returnTo = chr->_returnTo;
// if (returnTo != Chr::RETURN_TO_STORAGE) {
// Common::String returnToSceneName;
// if (returnTo == Chr::RETURN_TO_INITIAL_SCENE) {
// returnToSceneName = chr->_initialScene;
// returnToSceneName.toLowercase();
// } else {
// returnToSceneName = "random@";
// }
// Scene *scene = getSceneByName(returnToSceneName);
// if (scene != NULL && scene != _world->_storageScene) {
// _world->move(chr, scene);
// // To avoid sleeping twice, return if the above move command would cause a sleep.
// if (scene == currentScene)
// return;
// }
// }
//}
if (to == player->_currentScene) {
if (getMonster() == NULL) {
_monster = chr;
encounter(player, chr);
}
}
}
if (!_temporarilyHidden) {
if (to == currentScene || from == currentScene) {
redrawScene();
g_system->updateScreen();
g_system->delayMillis(100);
}
}
}
void WageEngine::redrawScene() {
Scene *currentScene = _world->_player->_currentScene;
if (currentScene != NULL) {
bool firstTime = (_lastScene != currentScene);
_gui->draw();
updateSoundTimerForScene(currentScene, firstTime);
}
}
void WageEngine::processTurnInternal(Common::String *textInput, Designed *clickInput) {
Scene *playerScene = _world->_player->_currentScene;
if (playerScene == _world->_storageScene)
return;
bool shouldEncounter = false;
if (playerScene != _lastScene) {
_loopCount = 0;
_lastScene = playerScene;
_monster = NULL;
_running = NULL;
_offer = NULL;
for (ChrList::const_iterator it = playerScene->_chrs.begin(); it != playerScene->_chrs.end(); ++it) {
if (!(*it)->_playerCharacter) {
_monster = *it;
shouldEncounter = true;
break;
}
}
}
bool monsterWasNull = (_monster == NULL);
Script *script = playerScene->_script != NULL ? playerScene->_script : _world->_globalScript;
bool handled = script->execute(_world, _loopCount++, textInput, clickInput);
playerScene = _world->_player->_currentScene;
if (playerScene == _world->_storageScene)
return;
if (playerScene != _lastScene) {
_temporarilyHidden = true;
_gui->clearOutput();
_gui->_consoleWindow->setTextWindowFont(_world->_player->_currentScene->getFont());
_world->_commandsMenu = _world->_commandsMenuDefault;
_gui->regenCommandsMenu();
regen();
sayText(playerScene->_name, Common::TextToSpeechManager::QUEUE);
Common::String input("look");
processTurnInternal(&input, NULL);
if (_shouldQuit)
return;
// WORKAROUND: The original Java codebase did not have this check and
// called gameOver() only in onMove() method. However, this leads to a crash in
// Gui::redraw(), when _engine->_world->_player->_currentScene is equal to _world->_storageScene.
// The crash happens because storage scene's _designBounds member is NULL.
// Therefore, to fix this, we check and call gameOver() here if needed.
if (_world->_player->_currentScene == _world->_storageScene) {
if (!_isGameOver) {
_isGameOver = true;
_gui->gameOver();
}
}
redrawScene();
_temporarilyHidden = false;
} else if (_loopCount == 1) {
redrawScene();
if (shouldEncounter && getMonster() != NULL) {
encounter(_world->_player, _monster);
}
} else if (textInput != NULL && !handled) {
if (monsterWasNull && getMonster() != NULL)
return;
const char *rant = _rnd->getRandomNumber(1) ? "What?" : "Huh?";
appendText(rant);
_commandWasQuick = true;
}
}
void WageEngine::processTurn(Common::String *textInput, Designed *clickInput) {
_commandWasQuick = false;
Scene *prevScene = _world->_player->_currentScene;
Chr *prevMonster = _monster;
Chr *runner = _running;
Common::String input;
if (textInput)
input = *textInput;
input.toLowercase();
// if the player is frozen, we loop automatically to process enemy turns
// without waiting for user input
while (_world->_player->_context._frozen) {
// decrement Timer
_world->_player->_context._freezeTimer--;
if (_world->_player->_context._freezeTimer <= 0) {
_world->_player->_context._frozen = false;
_world->_player->_context._freezeTimer = 0;
// we break the loop. The player regains control for the next input
break;
}
// enemy gets a free attack
if (getMonster() != NULL)
performCombatAction(getMonster(), _world->_player);
// since we are inside a while loop, we must
// force a screen update or the text will not appear until the end
if (_gui) _gui->draw();
g_system->updateScreen();
// if player died during freeze, return
if (_isGameOver || _world->_player->_currentScene == _world->_storageScene)
return;
}
// only process the player's input if they are not frozen
if (!_world->_player->_context._frozen)
processTurnInternal(&input, clickInput);
Scene *playerScene = _world->_player->_currentScene;
if (prevScene != playerScene && playerScene != _world->_storageScene) {
if (prevMonster != NULL) {
bool followed = false;
bool monsterEscaped = false;
// check if the previous monster followed us to the new room
if (prevMonster->_currentScene != playerScene) {
// monster is gone did it escape?
// if the monster we were fighting was running, and is now in storage that means it escaped
if (prevMonster == runner && prevMonster->_currentScene == _world->_storageScene) {
char buf[512];
snprintf(buf, 512, "%s%s escapes!", prevMonster->getDefiniteArticle(true), prevMonster->_name.c_str());
appendText(buf);
monsterEscaped = true;
}
// TODO: adjacent scenes doesn't contain up/down etc... verify that monsters can't follow these...
// only check follow logic if monster did not just escape to storage
if (!monsterEscaped) {
if (_world->scenesAreConnected(playerScene, prevMonster->_currentScene)) {
int chance = _rnd->getRandomNumber(255);
followed = (chance < prevMonster->_followsOpponent);
}
}
}
char buf[512];
if (followed) {
snprintf(buf, 512, "%s%s follows you.", prevMonster->getDefiniteArticle(true), prevMonster->_name.c_str());
appendText(buf);
_world->move(prevMonster, playerScene);
} else if (!monsterEscaped) {
// only say "You escape" if the monster did not already "Escape"
snprintf(buf, 512, "You escape %s%s.", prevMonster->getDefiniteArticle(false), prevMonster->_name.c_str());
appendText(buf);
}
}
}
if (!_commandWasQuick && getMonster() != NULL) {
performCombatAction(getMonster(), _world->_player);
}
_inputText.clear();
}
} // End of namespace Wage

268
engines/wage/wage.h Normal file
View File

@@ -0,0 +1,268 @@
/* 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/>.
*
* MIT License:
*
* Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#ifndef WAGE_WAGE_H
#define WAGE_WAGE_H
#include "engines/engine.h"
#include "audio/mixer.h"
#include "common/debug.h"
#include "common/endian.h"
#include "common/rect.h"
#include "common/macresman.h"
#include "common/random.h"
#include "common/timer.h"
#include "common/text-to-speech.h"
#include "wage/debugger.h"
struct ADGameDescription;
namespace Common {
struct Event;
}
namespace Graphics {
class MacDialog;
}
namespace Audio {
class PCSpeaker;
}
namespace Wage {
class Console;
class Chr;
class Designed;
class Dialog;
class Gui;
class Obj;
class Scene;
class World;
typedef Common::Array<Obj *> ObjArray;
typedef Common::Array<Chr *> ChrArray;
typedef Common::List<Obj *> ObjList;
typedef Common::List<Chr *> ChrList;
#define STORAGESCENE "STORAGE@"
enum {
kDebugImGui = 1,
kDebugSound,
kDebugLoading,
};
enum OperandType {
OBJ = 0,
CHR = 1,
SCENE = 2,
NUMBER = 3,
STRING = 4,
CLICK_INPUT = 5,
TEXT_INPUT = 6,
UNKNOWN = 100
};
enum Directions {
NORTH = 0,
SOUTH = 1,
EAST = 2,
WEST = 3
};
enum Resolution {
GF_RES800 = 1 << 0,
GF_RES1024 = 1 << 1
};
Common::Rect *readRect(Common::SeekableReadStream *in);
const char *getIndefiniteArticle(const Common::String &word);
const char *prependGenderSpecificPronoun(int gender);
const char *getGenderSpecificPronoun(int gender, bool capitalize);
bool isStorageScene(const Common::String &name);
class WageEngine : public Engine {
friend class Dialog;
public:
WageEngine(OSystem *syst, const ADGameDescription *gameDesc);
~WageEngine() override;
bool hasFeature(EngineFeature f) const override;
Common::Error run() override;
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override;
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
const char *getGameFile() const;
void processTurn(Common::String *textInput, Designed *clickInput);
void regen();
const char *getTargetName() { return _targetName.c_str(); }
bool pollEvent(Common::Event &event);
private:
bool loadWorld(Common::MacResManager *resMan);
void performInitialSetup();
void wearObjs(Chr *chr);
void processTurnInternal(Common::String *textInput, Designed *clickInput);
void performCombatAction(Chr *npc, Chr *player);
int getValidMoveDirections(Chr *npc);
void performAttack(Chr *attacker, Chr *victim, Obj *weapon);
void performMagic(Chr *attacker, Chr *victim, Obj *magicalObject);
void performMove(Chr *chr, int validMoves);
void performOffer(Chr *attacker, Chr *victim);
void performTake(Chr *npc, Obj *obj);
void decrementUses(Obj *obj);
bool attackHit(Chr *attacker, Chr *victim, Obj *weapon, int targetIndex);
void performHealingMagic(Chr *chr, Obj *magicalObject);
public:
void takeObj(Obj *obj);
bool handleMoveCommand(Directions dir, const char *dirName);
bool handleLookCommand();
Common::String *getGroundItemsList(Scene *scene);
void appendObjNames(Common::String &str, const ObjArray &objs);
bool handleInventoryCommand();
bool handleStatusCommand();
bool handleRestCommand();
bool handleAcceptCommand();
bool handleTakeCommand(const char *target);
bool handleDropCommand(const char *target);
bool handleAimCommand(const char *target);
bool handleWearCommand(const char *target);
bool handleOfferCommand(const char *target);
void wearObj(Obj *o, int pos);
bool tryAttack(const Obj *weapon, const Common::String &input);
bool handleAttack(Obj *weapon);
void printPlayerCondition(Chr *player);
const char *getPercentMessage(double percent);
void doClose();
public:
Common::RandomSource *_rnd;
Gui *_gui;
World *_world;
Scene *_lastScene;
int _loopCount;
int _turn;
Chr *_monster;
Chr *_running;
Obj *_offer;
int _aim;
int _opponentAim;
bool _temporarilyHidden;
bool _isGameOver;
bool _commandWasQuick;
bool _restartRequested = false;
bool _shouldQuit;
int _defaultSaveSlot = -1;
Common::String _defaultSaveDescritpion;
Common::String _inputText;
Common::List<int> _soundQueue;
Common::String _soundToPlay;
Audio::PCSpeaker *_speaker;
void playSound(Common::String soundName, bool blocking = true);
void updateSoundTimerForScene(Scene *scene, bool firstTime);
void setMenu(Common::String soundName);
void appendText(const char *str);
void sayText(const Common::U32String &str, Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::INTERRUPT_NO_REPEAT) const;
void sayText(const Common::String &str, Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::INTERRUPT_NO_REPEAT) const;
Obj *getOffer();
Chr *getMonster();
void processEvents();
Scene *getSceneByName(Common::String &location);
void onMove(Designed *what, Designed *from, Designed *to);
void encounter(Chr *player, Chr *chr);
void redrawScene();
void saveGame();
uint32 getFeatures();
Common::Error loadGameState(int slot) override;
Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
bool scummVMSaveLoadDialog(bool isSave);
private:
int getSceneIndex(Scene *scene) const;
Obj *getObjByOffset(int offset, int objBaseOffset) const;
Chr *getChrById(int resId) const;
Chr *getChrByOffset(int offset, int chrBaseOffset) const;
Scene *getSceneById(int id) const;
Scene *getSceneByOffset(int offset) const;
int saveGame(const Common::String &fileName, const Common::String &descriptionString);
int loadGame(int slotId);
void resetState();
void restart();
private:
const ADGameDescription *_gameDescription;
Common::MacResManager *_resManager;
Audio::SoundHandle _soundHandle;
};
extern WageEngine *g_wage;
} // End of namespace Wage
#endif

571
engines/wage/world.cpp Normal file
View File

@@ -0,0 +1,571 @@
/* 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/>.
*
* MIT License:
*
* Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#include "common/file.h"
#include "graphics/macgui/macwindowmanager.h"
#include "graphics/macgui/macfontmanager.h"
#include "graphics/macgui/macmenu.h"
#include "graphics/fontman.h"
#include "wage/wage.h"
#include "wage/entities.h"
#include "wage/script.h"
#include "wage/sound.h"
#include "wage/world.h"
namespace Wage {
World::World(WageEngine *engine) {
_storageScene = new Scene;
_storageScene->_name = STORAGESCENE;
_orderedScenes.push_back(_storageScene);
_scenes[STORAGESCENE] = _storageScene;
_gameOverMessage = nullptr;
_saveBeforeQuitMessage = nullptr;
_saveBeforeCloseMessage = nullptr;
_revertMessage = nullptr;
_globalScript = nullptr;
_player = nullptr;
_signature = 0;
_weaponMenuDisabled = true;
_engine = engine;
_patterns = new Graphics::MacPatterns;
}
World::~World() {
for (uint i = 0; i < _orderedObjs.size(); i++)
delete _orderedObjs[i];
for (uint i = 0; i < _orderedChrs.size(); i++)
delete _orderedChrs[i];
for (uint i = 0; i < _orderedSounds.size(); i++)
delete _orderedSounds[i];
for (uint i = 0; i < _orderedScenes.size(); i++)
delete _orderedScenes[i];
for (uint i = 0; i < _patterns->size(); i++)
free(const_cast<byte *>(_patterns->operator[](i)));
delete _patterns;
delete _globalScript;
delete _gameOverMessage;
delete _saveBeforeQuitMessage;
delete _saveBeforeCloseMessage;
delete _revertMessage;
}
bool World::loadWorld(Common::MacResManager *resMan) {
Common::MacResIDArray resArray;
Common::SeekableReadStream *res;
Common::MacResIDArray::const_iterator iter;
// Dumping interpreter code
#if 0
res = resMan->getResource(MKTAG('C','O','D','E'), 1);
warning("Dumping interpreter code size: %d", res->size());
byte *buf = (byte *)malloc(res->size());
res->read(buf, res->size());
Common::DumpFile out;
out.open("code.bin");
out.write(buf, res->size());
out.close();
free(buf);
delete res;
#endif
if ((resArray = resMan->getResIDArray(MKTAG('G','C','O','D'))).size() == 0)
return false;
// Load global script
res = resMan->getResource(MKTAG('G','C','O','D'), resArray[0]);
_globalScript = new Script(res, -1, _engine);
// TODO: read creator
// Load main configuration
if ((resArray = resMan->getResIDArray(MKTAG('V','E','R','S'))).size() == 0)
return false;
_name = resMan->getBaseFileName().toString();
if (resArray.size() > 1)
warning("Too many VERS resources");
if (!resArray.empty()) {
debug(3, "Loading version info");
res = resMan->getResource(MKTAG('V','E','R','S'), resArray[0]);
_signature = res->readSint32LE();
res->skip(6);
byte b = res->readByte();
_weaponMenuDisabled = (b != 0);
if (b != 0 && b != 1)
error("Unexpected value for weapons menu");
res->skip(3);
_aboutMessage = res->readPascalString();
if (!scumm_stricmp(_name.c_str(), "Scepters"))
res->skip(1); // ????
_soundLibrary1 = Common::Path(res->readPascalString());
_soundLibrary2 = Common::Path(res->readPascalString());
delete res;
}
Common::String *message;
if ((message = loadStringFromDITL(resMan, 2910, 1)) != NULL) {
message->trim();
debug(2, "_gameOverMessage: %s", message->c_str());
_gameOverMessage = message;
} else {
_gameOverMessage = new Common::String("Game Over!");
}
if ((message = loadStringFromDITL(resMan, 2480, 3)) != NULL) {
message->trim();
debug(2, "_saveBeforeQuitMessage: %s", message->c_str());
_saveBeforeQuitMessage = message;
} else {
_saveBeforeQuitMessage = new Common::String("Save changes before quiting?");
}
if ((message = loadStringFromDITL(resMan, 2490, 3)) != NULL && !message->empty()) {
message->trim();
debug(2, "_saveBeforeCloseMessage: %s", message->c_str());
_saveBeforeCloseMessage = message;
} else {
_saveBeforeCloseMessage = new Common::String("Save changes before closing?");
}
if ((message = loadStringFromDITL(resMan, 2940, 2)) != NULL) {
message->trim();
debug(2, "_revertMessage: %s", message->c_str());
_revertMessage = message;
} else {
_revertMessage = new Common::String("Revert to the last saved version?");
}
// Load scenes
resArray = resMan->getResIDArray(MKTAG('A','S','C','N'));
debug(3, "Loading %d scenes", resArray.size());
for (iter = resArray.begin(); iter != resArray.end(); ++iter) {
res = resMan->getResource(MKTAG('A','S','C','N'), *iter);
Scene *scene = new Scene(resMan->getResName(MKTAG('A','S','C','N'), *iter), res);
res = resMan->getResource(MKTAG('A','C','O','D'), *iter);
if (res != NULL)
scene->_script = new Script(res, *iter, _engine);
res = resMan->getResource(MKTAG('A','T','X','T'), *iter);
if (res != NULL) {
scene->_textBounds = readRect(res);
int fontType = res->readUint16BE();
// WORKAROUND: Dune Eternity has a weird fontType ID so we override it to the correct one
if (_name == "***DUNE ETERNITY*** ")
fontType = 3;
int fontSize = res->readUint16BE();
scene->_font = new Graphics::MacFont(fontType, fontSize, Graphics::kMacFontRegular);
const Graphics::Font *fallback = FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont);
scene->_font->setFallback(fallback);
Common::String text;
while (res->pos() < res->size()) {
char c = res->readByte();
if (c == 0x0d)
c = '\n';
text += c;
}
scene->_text = text;
delete res;
}
scene->_resourceId = *iter;
addScene(scene);
}
// Load Objects
resArray = resMan->getResIDArray(MKTAG('A','O','B','J'));
debug(3, "Loading %d objects", resArray.size());
for (iter = resArray.begin(); iter != resArray.end(); ++iter) {
res = resMan->getResource(MKTAG('A','O','B','J'), *iter);
addObj(new Obj(resMan->getResName(MKTAG('A','O','B','J'), *iter), res, *iter));
}
// Load Characters
resArray = resMan->getResIDArray(MKTAG('A','C','H','R'));
debug(3, "Loading %d characters", resArray.size());
for (iter = resArray.begin(); iter != resArray.end(); ++iter) {
res = resMan->getResource(MKTAG('A','C','H','R'), *iter);
Chr *chr = new Chr(resMan->getResName(MKTAG('A','C','H','R'), *iter), res);
chr->_resourceId = *iter;
addChr(chr);
if (chr->_playerCharacter) {
if (_player)
warning("loadWorld: Player is redefined");
_player = chr;
}
}
if (!_player) {
warning("loadWorld: Player is not defined");
if (_chrs.empty()) {
error("loadWorld: and I have no characters");
}
_player = _orderedChrs[0];
}
// Load Sounds
resArray = resMan->getResIDArray(MKTAG('A','S','N','D'));
debug(3, "Loading %d sounds", resArray.size());
for (iter = resArray.begin(); iter != resArray.end(); ++iter) {
res = resMan->getResource(MKTAG('A','S','N','D'), *iter);
addSound(new Sound(resMan->getResName(MKTAG('A','S','N','D'), *iter), res));
}
if (!_soundLibrary1.empty()) {
loadExternalSounds(_soundLibrary1);
}
if (!_soundLibrary2.empty()) {
loadExternalSounds(_soundLibrary2);
}
// Load Patterns
res = resMan->getResource(MKTAG('P','A','T','#'), 900);
if (res != NULL) {
int count = res->readUint16BE();
debug(3, "Loading %d patterns", count);
for (int i = 0; i < count; i++) {
byte *pattern = (byte *)malloc(8);
res->read(pattern, 8);
_patterns->push_back(pattern);
}
delete res;
} else {
/* Enchanted Scepters did not use the PAT# resource for the textures. */
res = resMan->getResource(MKTAG('C','O','D','E'), 1);
if (res != NULL) {
const char *magic = "\x92\x40\x15\x81\x20\x00\x4E\x75";
int cnt = 0;
bool found = false;
while (!res->eos()) {
byte b = res->readByte();
if (b == (byte)magic[cnt]) {
cnt++;
if (cnt == 8) {
found = true;
break;
}
} else {
cnt = 0;
}
}
if (!found)
error("World::loadWorld(): Could not find Enhanced Scepters' patterns");
res->skip(8); // Skip 8 more bytes
debug(3, "Loading 29 patterns for Enhanced Scepters at %" PRId64, res->pos());
for (int i = 0; i < 29; i++) {
byte *pattern = (byte *)malloc(8);
res->read(pattern, 8);
_patterns->push_back(pattern);
}
}
delete res;
}
res = resMan->getResource(MKTAG('M','E','N','U'), 2001);
if (res != NULL) {
Common::StringArray *menu = Graphics::MacMenu::readMenuFromResource(res);
_aboutMenuItemName.clear();
Common::String string = menu->operator[](1);
for (uint i = 0; i < string.size() && string[i] != ';'; i++) // Read token
_aboutMenuItemName += string[i];
debugC(1, kDebugLoading, "MENU: About: %s", toPrintable(_aboutMenuItemName).c_str());
delete menu;
delete res;
}
res = resMan->getResource(MKTAG('M','E','N','U'), 2004);
if (res != NULL) {
Common::StringArray *menu = Graphics::MacMenu::readMenuFromResource(res);
_commandsMenuName = menu->operator[](0);
_commandsMenuDefault = menu->operator[](1);
_commandsMenu = _commandsMenuDefault;
debugC(1, kDebugLoading, "MENU: Commands name: %s", toPrintable(_commandsMenuName).c_str());
debugC(1, kDebugLoading, "MENU: Commands menu: %s", toPrintable(_commandsMenu).c_str());
delete menu;
delete res;
}
res = resMan->getResource(MKTAG('M','E','N','U'), 2005);
if (res != NULL) {
Common::StringArray *menu = Graphics::MacMenu::readMenuFromResource(res);
_weaponsMenuName = menu->operator[](0);
delete menu;
delete res;
debugC(1, kDebugLoading, "MENU: Weapons name: %s", toPrintable(_weaponsMenuName).c_str());
}
// TODO: Read Apple menu and get the name of that menu item..
// store global info in state object for use with save/load actions
//world.setCurrentState(initialState); // pass off the state object to the world
return true;
}
void World::addSound(Sound *sound) {
Common::String s = sound->_name;
s.toLowercase();
_sounds[s] = sound;
_orderedSounds.push_back(sound);
}
void World::loadExternalSounds(const Common::Path &fname) {
Common::MacResManager resMan;
if (!resMan.open(fname)) {
warning("Cannot load sound file <%s>", fname.toString().c_str());
return;
}
Common::MacResIDArray resArray;
Common::SeekableReadStream *res;
Common::MacResIDArray::const_iterator iter;
resArray = resMan.getResIDArray(MKTAG('A','S','N','D'));
for (iter = resArray.begin(); iter != resArray.end(); ++iter) {
res = resMan.getResource(MKTAG('A','S','N','D'), *iter);
if (!res) {
warning("Cannot load sound resource %d from file <%s>", *iter, fname.toString().c_str());
continue;
}
addSound(new Sound(resMan.getResName(MKTAG('A','S','N','D'), *iter), res));
}
}
Common::String *World::loadStringFromDITL(Common::MacResManager *resMan, int resourceId, int itemIndex) {
Common::SeekableReadStream *res = resMan->getResource(MKTAG('D','I','T','L'), resourceId);
if (res) {
int itemCount = res->readSint16BE();
for (int i = 0; i <= itemCount; i++) {
// int placeholder; short rect[4]; byte flags; pstring str;
res->skip(13);
Common::String message = res->readPascalString();
if (i == itemIndex) {
Common::String *msg = new Common::String(message);
delete res;
return msg;
}
}
delete res;
}
return NULL;
}
static bool invComparator(const Obj *l, const Obj *r) {
return l->_index < r->_index;
}
void World::move(Obj *obj, Chr *chr) {
if (obj == NULL)
return;
Designed *from = obj->removeFromCharOrScene();
obj->setCurrentOwner(chr);
chr->_inventory.push_back(obj);
Common::sort(chr->_inventory.begin(), chr->_inventory.end(), invComparator);
_engine->onMove(obj, from, chr);
}
static bool objComparator(const Obj *o1, const Obj *o2) {
bool o1Immobile = (o1->_type == Obj::IMMOBILE_OBJECT);
bool o2Immobile = (o2->_type == Obj::IMMOBILE_OBJECT);
if (o1Immobile == o2Immobile) {
return o1->_index < o2->_index;
}
return o1Immobile;
}
void World::move(Obj *obj, Scene *scene, bool skipSort) {
if (obj == NULL)
return;
Designed *from = obj->removeFromCharOrScene();
obj->setCurrentScene(scene);
scene->_objs.push_back(obj);
if (!skipSort)
Common::sort(scene->_objs.begin(), scene->_objs.end(), objComparator);
_engine->onMove(obj, from, scene);
}
static bool chrComparator(const Chr *l, const Chr *r) {
return l->_index < r->_index;
}
void World::move(Chr *chr, Scene *scene, bool skipSort) {
if (chr == NULL)
return;
Scene *from = chr->_currentScene;
if (from == scene)
return;
if (from != NULL)
from->_chrs.remove(chr);
scene->_chrs.push_back(chr);
if (!skipSort)
Common::sort(scene->_chrs.begin(), scene->_chrs.end(), chrComparator);
if (scene == _storageScene) {
chr->resetState();
} else if (chr->_playerCharacter) {
scene->_visited = true;
_player->_context._visits++;
}
chr->_currentScene = scene;
_engine->onMove(chr, from, scene);
}
Scene *World::getRandomScene() {
// Not including storage:
return _orderedScenes[1 + _engine->_rnd->getRandomNumber(_orderedScenes.size() - 2)];
}
Scene *World::getSceneAt(int x, int y) {
for (uint i = 0; i < _orderedScenes.size(); i++) {
Scene *scene = _orderedScenes[i];
if (scene != _storageScene && scene->_worldX == x && scene->_worldY == y) {
return scene;
}
}
return NULL;
}
static const int directionsX[] = { 0, 0, 1, -1 };
static const int directionsY[] = { -1, 1, 0, 0 };
bool World::scenesAreConnected(Scene *scene1, Scene *scene2) {
if (!scene1 || !scene2)
return false;
int x = scene2->_worldX;
int y = scene2->_worldY;
for (int dir = 0; dir < 4; dir++)
if (!scene2->_blocked[dir])
if (getSceneAt(x + directionsX[dir], y + directionsY[dir]) == scene1)
return true;
return false;
}
const char *World::getAboutMenuItemName() {
static char menu[256];
*menu = '\0';
if (_aboutMenuItemName.empty()) {
Common::sprintf_s(menu, "About %s...", _name.c_str());
} else { // Replace '@' with name
const char *str = _aboutMenuItemName.c_str();
const char *pos = strchr(str, '@');
if (pos) {
strncat(menu, str, (pos - str));
strncat(menu, _name.c_str(), 255);
strncat(menu, pos + 1, 255);
} else {
Common::strlcpy(menu, _aboutMenuItemName.c_str(), 256);
}
}
debugC(1, kDebugLoading, "MENU: About cleansed: %s", Common::toPrintable(menu).c_str());
return menu;
}
} // End of namespace Wage

141
engines/wage/world.h Normal file
View File

@@ -0,0 +1,141 @@
/* 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/>.
*
* MIT License:
*
* Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#ifndef WAGE_WORLD_H
#define WAGE_WORLD_H
#include "wage/entities.h"
#include "graphics/macgui/macwindowmanager.h"
namespace Wage {
// Import the enum definitions
using Graphics::MacPatterns;
class Script;
class Sound;
class World {
public:
World(WageEngine *engine);
~World();
bool loadWorld(Common::MacResManager *resMan);
void loadExternalSounds(const Common::Path &fname);
Common::String *loadStringFromDITL(Common::MacResManager *resMan, int resourceId, int itemIndex);
void move(Obj *obj, Chr *chr);
void move(Obj *obj, Scene *scene, bool skipSort = false);
void move(Chr *chr, Scene *scene, bool skipSort = false);
Scene *getRandomScene();
Scene *getSceneAt(int x, int y);
bool scenesAreConnected(Scene *scene1, Scene *scene2);
const char *getAboutMenuItemName();
WageEngine *_engine;
Common::String _name;
Common::String _aboutMessage;
Common::Path _soundLibrary1;
Common::Path _soundLibrary2;
bool _weaponMenuDisabled;
Script *_globalScript;
Common::HashMap<Common::String, Scene *> _scenes;
Common::HashMap<Common::String, Obj *> _objs;
Common::HashMap<Common::String, Chr *> _chrs;
Common::HashMap<Common::String, Sound *> _sounds;
Common::Array<Scene *> _orderedScenes;
ObjArray _orderedObjs;
ChrArray _orderedChrs;
Common::Array<Sound *> _orderedSounds;
Graphics::MacPatterns *_patterns;
Scene *_storageScene;
Chr *_player;
int _signature;
//List<MoveListener> moveListeners;
Common::String *_gameOverMessage;
Common::String *_saveBeforeQuitMessage;
Common::String *_saveBeforeCloseMessage;
Common::String *_revertMessage;
Common::String _aboutMenuItemName;
Common::String _commandsMenuName;
Common::String _commandsMenu;
Common::String _commandsMenuDefault;
Common::String _weaponsMenuName;
void addScene(Scene *room) {
if (!room->_name.empty()) {
Common::String s = room->_name;
s.toLowercase();
_scenes[s] = room;
}
_orderedScenes.push_back(room);
}
void addObj(Obj *obj) {
Common::String s = obj->_name;
s.toLowercase();
_objs[s] = obj;
obj->_index = _orderedObjs.size();
_orderedObjs.push_back(obj);
}
void addChr(Chr *chr) {
Common::String s = chr->_name;
s.toLowercase();
_chrs[s] = chr;
chr->_index = _orderedChrs.size();
_orderedChrs.push_back(chr);
}
void addSound(Sound *sound);
};
} // End of namespace Wage
#endif