aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugene Sandulenko2016-02-14 17:20:25 +0100
committerEugene Sandulenko2016-02-14 17:20:25 +0100
commit11e03fbce271d4817225d834349e5f2e276353ef (patch)
treec1caae1cc8318698ec3306cfee6e8d4b46a9a8be
parente7773825f2ea3e7901d59de8e31bc223d1bd7ac0 (diff)
parent499519fec3820d5f5ad377953560c321ac3efcab (diff)
downloadscummvm-rg350-11e03fbce271d4817225d834349e5f2e276353ef.tar.gz
scummvm-rg350-11e03fbce271d4817225d834349e5f2e276353ef.tar.bz2
scummvm-rg350-11e03fbce271d4817225d834349e5f2e276353ef.zip
Merge pull request #658 from sev-/wage
WAGE: New Engine
-rwxr-xr-xdevtools/create_wage/create_wage.sh119
-rw-r--r--engines/wage/combat.cpp914
-rw-r--r--engines/wage/configure.engine3
-rw-r--r--engines/wage/design.cpp538
-rw-r--r--engines/wage/design.h115
-rw-r--r--engines/wage/detection.cpp238
-rw-r--r--engines/wage/dialog.cpp241
-rw-r--r--engines/wage/dialog.h101
-rw-r--r--engines/wage/entities.cpp500
-rw-r--r--engines/wage/entities.h345
-rw-r--r--engines/wage/gui-console.cpp398
-rw-r--r--engines/wage/gui.cpp594
-rw-r--r--engines/wage/gui.h178
-rw-r--r--engines/wage/menu.cpp563
-rw-r--r--engines/wage/menu.h138
-rw-r--r--engines/wage/module.mk27
-rw-r--r--engines/wage/randomhat.cpp83
-rw-r--r--engines/wage/randomhat.h77
-rw-r--r--engines/wage/script.cpp1146
-rw-r--r--engines/wage/script.h153
-rw-r--r--engines/wage/util.cpp123
-rw-r--r--engines/wage/wage.cpp504
-rw-r--r--engines/wage/wage.h234
-rw-r--r--engines/wage/world.cpp510
-rw-r--r--engines/wage/world.h141
-rw-r--r--graphics/primitives.cpp349
-rw-r--r--graphics/primitives.h11
27 files changed, 8343 insertions, 0 deletions
diff --git a/devtools/create_wage/create_wage.sh b/devtools/create_wage/create_wage.sh
new file mode 100755
index 0000000000..5e8fe352a2
--- /dev/null
+++ b/devtools/create_wage/create_wage.sh
@@ -0,0 +1,119 @@
+#!/bin/bash
+#
+# This script downloads System 7.0.1 image from Apple and extracts fonts
+# from it. Mac only, unfortunately.
+#
+# On Windows you perhaps can perform the extraction manually with use of
+# HFV Explorer: https://web.archive.org/web/20011202005455/http://gamma.nic.fi/~lpesonen/HFVExplorer/
+#
+# More information could be found in the vMac documentation: http://www.gryphel.com/c/image/
+#
+# Alternatively you may use vMac instructions for extracting these disk images:
+# http://www.gryphel.com/c/minivmac/recipes/sys7inst/
+#
+# Based on instructions posted at
+# http://apple.stackexchange.com/questions/58243/can-i-get-the-original-mac-font-chicago-on-a-mountain-lion-mac
+
+echo_n() {
+ printf "$@"
+}
+
+if test `uname` != "Darwin"; then
+ echo This script is Mac OS X-only
+ exit
+fi
+
+echo_n "Downloading System 7.0.1 image..."
+if test ! -f System_7.0.1.smi.bin; then
+ curl -s http://download.info.apple.com/Apple_Support_Area/Apple_Software_Updates/English-North_American/Macintosh/System/Older_System/System_7.0.x/System_7.0.1.smi.bin -o System_7.0.1.smi.bin
+fi
+
+if test ! -f System_7.0.1.smi.bin; then
+ echo "Cannot download System_7.0.1.smi.bin"
+ exit
+fi
+
+echo done
+
+echo_n "Mounting System 7.0.1 image..."
+
+macbinary decode System_7.0.1.smi.bin
+hdiutil convert -quiet System\ 7.0.1.smi -format UDRO -o sys7.dmg
+hdiutil attach -quiet sys7.dmg
+
+if test ! -f /Volumes/7.0.1\ \(1440k.images\)/Fonts.image; then
+ echo "Failed to attach sys7.dmg"
+ exit
+fi
+
+echo done
+
+echo_n "Mounting Fonts disk image..."
+
+hdiutil convert -quiet /Volumes/7.0.1\ \(1440k.images\)/Fonts.image -format UDRO -o fonts.dmg
+hdiutil detach -quiet `hdiutil info|grep "/Volumes/7.0.1 (1440k.images)"|cut -f 1`
+hdiutil attach -quiet fonts.dmg
+
+if test ! -f /Volumes/Fonts/Chicago; then
+ echo "Failed to attach fonts.dmg"
+ exit
+fi
+
+echo done
+
+echo_n "Copying fonts..."
+
+for i in Athens Cairo Chicago Courier Geneva Helvetica London "Los Angeles" Monaco "New York" Palatino "San Francisco" Symbol Times Venice
+do
+ echo $i
+ macbinary encode "/Volumes/Fonts/$i" -o "$i.bin" -n
+done
+
+echo ...Done
+
+hdiutil detach -quiet `hdiutil info|grep "/Volumes/Fonts"|cut -f 1`
+
+if test ! -f fondu_src-060102.tgz; then
+ echo_n "Getting fondu_src-060102.tgz..."
+ curl -s http://fondu.sourceforge.net/fondu_src-060102.tgz -o fondu_src-060102.tgz
+ tar xf fondu_src-060102.tgz
+fi
+
+if test ! -d fondu-060102; then
+ echo "Failed to download fondu_src-060102.tgz"
+ exit
+fi
+
+echo done
+
+if test ! -x fondu-060102/fondu; then
+ echo_n "Compiling fondu..."
+ cd fondu-060102
+ ./configure >configure.log 2>&1 && make 2>&1 >make.log
+ cd ..
+fi
+
+if test ! -x fondu-060102/fondu; then
+ echo "Failed to build fondu. See configure.log and make.log"
+ exit
+else
+ rm -f configure.log make.log
+fi
+
+echo done
+
+echo_n "Converting fonts..."
+fondu-060102/fondu -force *.bin
+echo done
+
+zip -9 wage *.bdf
+mv wage.zip wage.dat
+
+echo_n "Cleaning up..."
+rm *.bdf
+rm *.ttf
+rm *.bin
+rm *.dmg
+echo done
+
+ls -l wage.dat
diff --git a/engines/wage/combat.cpp b/engines/wage/combat.cpp
new file mode 100644
index 0000000000..aa8ec535d2
--- /dev/null
+++ b/engines/wage/combat.cpp
@@ -0,0 +1,914 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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/randomhat.h"
+#include "wage/world.h"
+
+namespace Wage {
+
+Obj *WageEngine::getOffer() {
+ if (_offer != NULL) {
+ Chr *owner = _offer->_currentOwner;
+ if (owner == NULL || owner->_playerCharacter || owner->_currentScene != _world->_player->_currentScene) {
+ _offer = NULL;
+ }
+ }
+ return _offer;
+}
+
+Chr *WageEngine::getMonster() {
+ if (_monster != NULL && _monster->_currentScene != _world->_player->_currentScene) {
+ _monster = NULL;
+ }
+ return _monster;
+}
+
+void WageEngine::encounter(Chr *player, Chr *chr) {
+ char buf[512];
+
+ snprintf(buf, 512, "You encounter %s%s.", chr->_nameProperNoun ? "" : getIndefiniteArticle(chr->_name),
+ chr->_name.c_str());
+ appendText(buf);
+
+ if (!chr->_initialComment.empty())
+ appendText(chr->_initialComment.c_str());
+
+ if (chr->_armor[Chr::HEAD_ARMOR] != NULL) {
+ snprintf(buf, 512, "%s%s is wearing %s.", chr->getDefiniteArticle(true), chr->_name.c_str(),
+ getIndefiniteArticle(chr->_armor[Chr::HEAD_ARMOR]->_name));
+ appendText(buf);
+ }
+ if (chr->_armor[Chr::BODY_ARMOR] != NULL) {
+ snprintf(buf, 512, "%s is protected by %s%s.", getGenderSpecificPronoun(chr->_gender, true),
+ prependGenderSpecificPronoun(chr->_gender), chr->_armor[Chr::BODY_ARMOR]->_name.c_str());
+ appendText(buf);
+ }
+ if (chr->_armor[Chr::SHIELD_ARMOR] != NULL) {
+ Obj *obj = chr->_armor[Chr::SHIELD_ARMOR];
+
+ snprintf(buf, 512, "%s carries %s%s.", getGenderSpecificPronoun(chr->_gender, true),
+ obj->_namePlural ? "" : getIndefiniteArticle(obj->_name), obj->_name.c_str());
+ appendText(buf);
+ }
+}
+
+void WageEngine::performCombatAction(Chr *npc, Chr *player) {
+ if (npc->_context._frozen)
+ return;
+
+ RandomHat hat(_rnd);
+
+ bool winning = (npc->_context._statVariables[PHYS_HIT_CUR] > player->_context._statVariables[PHYS_HIT_CUR]);
+ int validMoves = getValidMoveDirections(npc);
+ ObjArray *weapons = npc->getWeapons(false);
+ ObjArray *magics = npc->getMagicalObjects();
+ // TODO: Figure out under what circumstances we need to add +1
+ // for the chance (e.g. only when all values were set to 0?).
+ if (winning) {
+ if (!_world->_weaponMenuDisabled) {
+ if (!weapons->empty())
+ hat.addTokens(kTokWeapons, npc->_winningWeapons + 1);
+ if (!magics->empty())
+ hat.addTokens(kTokMagic, npc->_winningMagic);
+ }
+ if (validMoves != 0)
+ hat.addTokens(kTokRun, npc->_winningRun + 1);
+ if (!npc->_inventory.empty())
+ hat.addTokens(kTokOffer, npc->_winningOffer + 1);
+ } else {
+ if (!_world->_weaponMenuDisabled) {
+ if (!weapons->empty())
+ hat.addTokens(kTokWeapons, npc->_losingWeapons + 1);
+ if (!magics->empty())
+ hat.addTokens(kTokMagic, npc->_losingMagic);
+ }
+ if (validMoves != 0)
+ hat.addTokens(kTokRun, npc->_losingRun + 1);
+ if (!npc->_inventory.empty())
+ hat.addTokens(kTokOffer, npc->_losingOffer + 1);
+ }
+
+ ObjList *objs = &npc->_currentScene->_objs;
+ if (npc->_inventory.size() < npc->_maximumCarriedObjects) {
+ int cnt = 0;
+ for (ObjList::const_iterator it = objs->begin(); it != objs->end(); ++it, ++cnt) {
+ if ((*it)->_type != Obj::IMMOBILE_OBJECT) {
+ // TODO: I'm not sure what the chance should be here.
+ hat.addTokens(cnt, 123);
+ }
+ }
+ }
+
+ int token = hat.drawToken();
+ switch (token) {
+ case kTokWeapons:
+ // TODO: I think the monster should choose the "best" weapon.
+ performAttack(npc, player, weapons->operator[](_rnd->getRandomNumber(weapons->size() - 1)));
+ break;
+ case kTokMagic:
+ // TODO: I think the monster should choose the "best" magic.
+ performMagic(npc, player, magics->operator[](_rnd->getRandomNumber(magics->size() - 1)));
+ break;
+ case kTokRun:
+ performMove(npc, validMoves);
+ break;
+ case kTokOffer:
+ performOffer(npc, player);
+ break;
+ case kTokNone:
+ break;
+ default:
+ {
+ int cnt = 0;
+ for (ObjList::const_iterator it = objs->begin(); it != objs->end(); ++it, ++cnt)
+ if (cnt == token)
+ performTake(npc, *it);
+ break;
+ }
+ }
+
+ delete weapons;
+ delete magics;
+}
+
+static const char *const targets[] = { "head", "chest", "side" };
+
+void WageEngine::performAttack(Chr *attacker, Chr *victim, Obj *weapon) {
+ if (_world->_weaponMenuDisabled)
+ return;
+
+ // TODO: verify that a player not aiming will always target the chest??
+ int targetIndex = -1;
+ char buf[256];
+
+ if (weapon->_type != Obj::MAGICAL_OBJECT) {
+ if (attacker->_playerCharacter) {
+ targetIndex = _aim;
+ } else {
+ targetIndex = _rnd->getRandomNumber(ARRAYSIZE(targets) - 1);
+ _opponentAim = targetIndex + 1;
+ }
+
+ if (!attacker->_playerCharacter) {
+ snprintf(buf, 256, "%s%s %ss %s%s at %s%s's %s.",
+ attacker->getDefiniteArticle(true), attacker->_name.c_str(),
+ weapon->_operativeVerb.c_str(),
+ prependGenderSpecificPronoun(attacker->_gender), weapon->_name.c_str(),
+ victim->getDefiniteArticle(true), victim->_name.c_str(),
+ targets[targetIndex]);
+ appendText(buf);
+ }
+ } else if (!attacker->_playerCharacter) {
+ snprintf(buf, 256, "%s%s %ss %s%s at %s%s.",
+ attacker->getDefiniteArticle(true), attacker->_name.c_str(),
+ weapon->_operativeVerb.c_str(),
+ prependGenderSpecificPronoun(attacker->_gender), weapon->_name.c_str(),
+ victim->getDefiniteArticle(true), victim->_name.c_str());
+ appendText(buf);
+ }
+
+ playSound(weapon->_sound);
+
+ bool usesDecremented = false;
+ int chance = _rnd->getRandomNumber(255);
+ // TODO: what about obj accuracy
+ if (chance < attacker->_physicalAccuracy) {
+ usesDecremented = attackHit(attacker, victim, weapon, targetIndex);
+ } else if (weapon->_type != Obj::MAGICAL_OBJECT) {
+ appendText("A miss!");
+ } else if (attacker->_playerCharacter) {
+ appendText("The spell has no effect.");
+ }
+
+ if (!usesDecremented) {
+ decrementUses(weapon);
+ }
+}
+
+void WageEngine::decrementUses(Obj *obj) {
+ int numberOfUses = obj->_numberOfUses;
+ if (numberOfUses != -1) {
+ numberOfUses--;
+ if (numberOfUses > 0) {
+ obj->_numberOfUses = numberOfUses;
+ } else {
+ if (!obj->_failureMessage.empty()) {
+ appendText(obj->_failureMessage.c_str());
+ }
+ if (obj->_returnToRandomScene) {
+ _world->move(obj, _world->getRandomScene());
+ } else {
+ _world->move(obj, _world->_storageScene);
+ }
+ obj->resetState(obj->_currentOwner, obj->_currentScene);
+ }
+ }
+}
+
+bool WageEngine::attackHit(Chr *attacker, Chr *victim, Obj *weapon, int targetIndex) {
+ bool receivedHitTextPrinted = false;
+ char buf[512];
+
+ if (targetIndex != -1) {
+ Obj *armor = victim->_armor[targetIndex];
+ if (armor != NULL) {
+ // TODO: Absorb some damage.
+ snprintf(buf, 512, "%s%s's %s weakens the impact of %s%s's %s.",
+ victim->getDefiniteArticle(true), victim->_name.c_str(),
+ victim->_armor[targetIndex]->_name.c_str(),
+ attacker->getDefiniteArticle(false), attacker->_name.c_str(),
+ weapon->_name.c_str());
+ appendText(buf);
+ decrementUses(armor);
+ } else {
+ snprintf(buf, 512, "A hit to the %s!", targets[targetIndex]);
+ appendText(buf);
+ }
+ playSound(attacker->_scoresHitSound);
+ appendText(attacker->_scoresHitComment.c_str());
+ playSound(victim->_receivesHitSound);
+ appendText(victim->_receivesHitComment.c_str());
+ receivedHitTextPrinted = true;
+ } else if (weapon->_type == Obj::MAGICAL_OBJECT) {
+ appendText(weapon->_useMessage.c_str());
+ appendText("The spell is effective!");
+ }
+
+ bool causesPhysicalDamage = true;
+ bool causesSpiritualDamage = false;
+ bool freezesOpponent = false;
+ bool usesDecremented = false;
+
+ if (weapon->_type == Obj::THROW_WEAPON) {
+ _world->move(weapon, victim->_currentScene);
+ } else if (weapon->_type == Obj::MAGICAL_OBJECT) {
+ int type = weapon->_attackType;
+ causesPhysicalDamage = (type == Obj::CAUSES_PHYSICAL_DAMAGE || type == Obj::CAUSES_PHYSICAL_AND_SPIRITUAL_DAMAGE);
+ causesSpiritualDamage = (type == Obj::CAUSES_SPIRITUAL_DAMAGE || type == Obj::CAUSES_PHYSICAL_AND_SPIRITUAL_DAMAGE);
+ freezesOpponent = (type == Obj::FREEZES_OPPONENT);
+ }
+
+ if (causesPhysicalDamage) {
+ victim->_context._userVariables[PHYS_HIT_CUR] -= weapon->_damage;
+
+ /* Do it here to get the right order of messages in case of death. */
+ decrementUses(weapon);
+ usesDecremented = true;
+
+ if (victim->_context._userVariables[PHYS_HIT_CUR] < 0) {
+ playSound(victim->_dyingSound);
+ appendText(victim->_dyingWords.c_str());
+ snprintf(buf, 512, "%s%s is dead!", victim->getDefiniteArticle(true), victim->_name.c_str());
+ appendText(buf);
+
+ attacker->_context._kills++;
+ attacker->_context._experience += victim->_context._userVariables[SPIR_HIT_CUR] + victim->_context._userVariables[PHYS_HIT_CUR];
+
+ if (!victim->_playerCharacter && !victim->_inventory.empty()) {
+ Scene *currentScene = victim->_currentScene;
+
+ for (int i = victim->_inventory.size() - 1; i >= 0; i--) {
+ _world->move(victim->_inventory[i], currentScene);
+ }
+ Common::String *s = getGroundItemsList(currentScene);
+ appendText(s->c_str());
+ delete s;
+ }
+ _world->move(victim, _world->_storageScene);
+ } else if (attacker->_playerCharacter && !receivedHitTextPrinted) {
+ double physicalPercent = (double)victim->_context._userVariables[SPIR_HIT_CUR] /
+ victim->_context._userVariables[SPIR_HIT_BAS];
+ snprintf(buf, 512, "%s%s's condition appears to be %s.",
+ victim->getDefiniteArticle(true), victim->_name.c_str(),
+ getPercentMessage(physicalPercent));
+ appendText(buf);
+ }
+ }
+
+ if (causesSpiritualDamage) {
+ /* TODO */
+ warning("TODO: Spiritual damage");
+ }
+
+ if (freezesOpponent) {
+ victim->_context._frozen = true;
+ }
+
+ return usesDecremented;
+}
+
+void WageEngine::performMagic(Chr *attacker, Chr *victim, Obj *magicalObject) {
+ switch (magicalObject->_attackType) {
+ case Obj::HEALS_PHYSICAL_DAMAGE:
+ case Obj::HEALS_SPIRITUAL_DAMAGE:
+ case Obj::HEALS_PHYSICAL_AND_SPIRITUAL_DAMAGE:
+ performHealingMagic(attacker, magicalObject);
+ return;
+ }
+
+ performAttack(attacker, victim, magicalObject);
+}
+
+void WageEngine::performHealingMagic(Chr *chr, Obj *magicalObject) {
+ char buf[512];
+
+ if (!chr->_playerCharacter) {
+ snprintf(buf, 512, "%s%s %ss %s%s.",
+ chr->getDefiniteArticle(true), chr->_name.c_str(),
+ magicalObject->_operativeVerb.c_str(),
+ getIndefiniteArticle(magicalObject->_name), magicalObject->_name.c_str());
+ appendText(buf);
+ }
+
+ uint chance = _rnd->getRandomNumber(255);
+ if (chance < magicalObject->_accuracy) {
+ int type = magicalObject->_attackType;
+
+ if (type == Obj::HEALS_PHYSICAL_DAMAGE || type == Obj::HEALS_PHYSICAL_AND_SPIRITUAL_DAMAGE)
+ chr->_context._statVariables[PHYS_HIT_CUR] += magicalObject->_damage;
+
+ if (type == Obj::HEALS_SPIRITUAL_DAMAGE || type == Obj::HEALS_PHYSICAL_AND_SPIRITUAL_DAMAGE)
+ chr->_context._statVariables[SPIR_HIT_CUR] += magicalObject->_damage;
+
+ playSound(magicalObject->_sound);
+ appendText(magicalObject->_useMessage.c_str());
+
+ // TODO: what if enemy heals himself?
+ if (chr->_playerCharacter) {
+ double physicalPercent = (double)chr->_context._statVariables[PHYS_HIT_CUR] / chr->_context._statVariables[PHYS_HIT_BAS];
+ double spiritualPercent = (double)chr->_context._statVariables[SPIR_HIT_CUR] / chr->_context._statVariables[SPIR_HIT_BAS];
+ snprintf(buf, 256, "Your physical condition is %s.", getPercentMessage(physicalPercent));
+ appendText(buf);
+
+ snprintf(buf, 256, "Your spiritual condition is %s.", getPercentMessage(spiritualPercent));
+ appendText(buf);
+ }
+ }
+
+ decrementUses(magicalObject);
+}
+
+static const int directionsX[] = { 0, 0, 1, -1 };
+static const int directionsY[] = { -1, 1, 0, 0 };
+static const char *const directionsS[] = { "north", "south", "east", "west" };
+
+void WageEngine::performMove(Chr *chr, int validMoves) {
+ // count how many valid moves we have
+ int numValidMoves = 0;
+
+ for (int dir = 0; dir < 4; dir++)
+ if ((validMoves & (1 << dir)) != 0)
+ numValidMoves++;
+
+ // Now pick random dir
+ int dir = _rnd->getRandomNumber(numValidMoves);
+
+ // And get it
+ for (int i = 0; i < 4; i++, dir--)
+ if ((validMoves & (1 << i)) != 0) {
+ if (dir == 1) {
+ dir = i;
+ break;
+ }
+ }
+
+ char buf[256];
+ snprintf(buf, 256, "%s%s runs %s.", chr->getDefiniteArticle(true), chr->_name.c_str(), directionsS[dir]);
+ appendText(buf);
+
+ _running = chr;
+ Scene *currentScene = chr->_currentScene;
+ int destX = currentScene->_worldX + directionsX[dir];
+ int destY = currentScene->_worldY + directionsY[dir];
+
+ _world->move(chr, _world->getSceneAt(destX, destY));
+}
+
+void WageEngine::performOffer(Chr *attacker, Chr *victim) {
+ /* TODO: choose in a smarter way? */
+ Obj *obj = attacker->_inventory[0];
+ char buf[512];
+
+ snprintf(buf, 512, "%s%s offers %s%s.", attacker->getDefiniteArticle(true), attacker->_name.c_str(),
+ obj->_namePlural ? "some " : getIndefiniteArticle(obj->_name), obj->_name.c_str());
+
+ appendText(buf);
+
+ _offer = obj;
+}
+
+void WageEngine::performTake(Chr *npc, Obj *obj) {
+ char buf[512];
+
+ snprintf(buf, 512, "%s%s picks up the %s%s.", npc->getDefiniteArticle(true), npc->_name.c_str(),
+ getIndefiniteArticle(obj->_name), obj->_name.c_str());
+
+ appendText(buf);
+
+ _world->move(obj, npc);
+}
+
+int WageEngine::getValidMoveDirections(Chr *npc) {
+ int directions = 0;
+ Scene *currentScene = npc->_currentScene;
+ for (int dir = 0; dir < 4; dir++) {
+ if (!currentScene->_blocked[dir]) {
+ int destX = currentScene->_worldX + directionsX[dir];
+ int destY = currentScene->_worldY + directionsY[dir];
+
+ Scene *scene = _world->getSceneAt(destX, destY);
+
+ if (scene != NULL && scene->_chrs.empty()) {
+ directions |= (1 << dir);
+ }
+ }
+ }
+
+ return directions;
+}
+
+void WageEngine::regen() {
+ Chr *player = _world->_player;
+ int curHp = player->_context._statVariables[PHYS_HIT_CUR];
+ int maxHp = player->_context._statVariables[PHYS_HIT_BAS];
+ int delta = maxHp - curHp;
+
+ if (delta > 0) {
+ int bonus = (int)(delta / (8 + _rnd->getRandomNumber(2)));
+ player->_context._statVariables[PHYS_HIT_CUR] += bonus;
+ }
+}
+
+void WageEngine::takeObj(Obj *obj) {
+ if (_world->_player->_inventory.size() >= _world->_player->_maximumCarriedObjects) {
+ appendText("Your pack is full, you must drop something.");
+ } else {
+ char buf[256];
+
+ _world->move(obj, _world->_player);
+ int type = _world->_player->wearObjIfPossible(obj);
+ if (type == Chr::HEAD_ARMOR) {
+ snprintf(buf, 256, "You are now wearing the %s.", obj->_name.c_str());
+ appendText(buf);
+ } else if (type == Chr::BODY_ARMOR) {
+ snprintf(buf, 256, "You are now wearing the %s.", obj->_name.c_str());
+ appendText(buf);
+ } else if (type == Chr::SHIELD_ARMOR) {
+ snprintf(buf, 256, "You are now wearing the %s.", obj->_name.c_str());
+ appendText(buf);
+ } else if (type == Chr::MAGIC_ARMOR) {
+ snprintf(buf, 256, "You are now wearing the %s.", obj->_name.c_str());
+ appendText(buf);
+ } else {
+ snprintf(buf, 256, "You now have the %s.", obj->_name.c_str());
+ appendText(buf);
+ }
+ appendText(obj->_clickMessage.c_str());
+ }
+}
+
+bool WageEngine::handleMoveCommand(Directions dir, const char *dirName) {
+ Scene *playerScene = _world->_player->_currentScene;
+ const char *msg = playerScene->_messages[dir].c_str();
+
+ if (!playerScene->_blocked[dir]) {
+ int destX = playerScene->_worldX + directionsX[dir];
+ int destY = playerScene->_worldY + directionsY[dir];
+
+ Scene *scene = _world->getSceneAt(destX, destY);
+
+ if (scene != NULL) {
+ if (strlen(msg) > 0) {
+ appendText(msg);
+ }
+ _world->move(_world->_player, scene);
+ return true;
+ }
+ }
+ if (strlen(msg) > 0) {
+ appendText(msg);
+ } else {
+ Common::String txt("You can't go ");
+ txt += dirName;
+ txt += ".";
+ appendText(txt.c_str());
+ }
+
+ return true;
+}
+
+bool WageEngine::handleLookCommand() {
+ appendText(_world->_player->_currentScene->_text.c_str());
+
+ Common::String *items = getGroundItemsList(_world->_player->_currentScene);
+ if (items != NULL) {
+ appendText(items->c_str());
+
+ delete items;
+ }
+
+ return true;
+}
+
+Common::String *WageEngine::getGroundItemsList(Scene *scene) {
+ ObjArray objs;
+
+ for (ObjList::const_iterator it = scene->_objs.begin(); it != scene->_objs.end(); ++it)
+ if ((*it)->_type != Obj::IMMOBILE_OBJECT)
+ objs.push_back(*it);
+
+ if (!objs.empty()) {
+ Common::String *res = new Common::String("On the ground you see ");
+ appendObjNames(*res, objs);
+ return res;
+ }
+ return NULL;
+}
+
+void WageEngine::appendObjNames(Common::String &str, const ObjArray &objs) {
+ for (uint i = 0; i < objs.size(); i++) {
+ Obj *obj = objs[i];
+
+ if (!obj->_namePlural)
+ str += getIndefiniteArticle(obj->_name);
+ else
+ str += "some ";
+
+ str += obj->_name;
+
+ if (i == objs.size() - 1) {
+ str += ".";
+ } else if (i == objs.size() - 2) {
+ if (objs.size() > 2)
+ str += ",";
+ str += " and ";
+ } else {
+ str += ", ";
+ }
+ }
+}
+
+bool WageEngine::handleInventoryCommand() {
+ Chr *player = _world->_player;
+ ObjArray objs;
+
+ for (ObjArray::const_iterator it = player->_inventory.begin(); it != player->_inventory.end(); ++it)
+ if (!player->isWearing(*it))
+ objs.push_back(*it);
+
+ if (objs.empty()) {
+ appendText("Your pack is empty.");
+ } else {
+ Common::String res("Your pack contains ");
+ appendObjNames(res, objs);
+ appendText(res.c_str());
+ }
+
+ return true;
+}
+
+static const char *const armorMessages[] = {
+ "Head protection:",
+ "Chest protection:",
+ "Shield protection:", // TODO: check message
+ "Magical protection:"
+};
+
+bool WageEngine::handleStatusCommand() {
+ Chr *player = _world->_player;
+ char buf[512];
+
+ snprintf(buf, 512, "Character name: %s%s", player->getDefiniteArticle(false), player->_name.c_str());
+ appendText(buf);
+ snprintf(buf, 512, "Experience: %d", player->_context._experience);
+ appendText(buf);
+
+ int wealth = 0;
+ for (ObjArray::const_iterator it = player->_inventory.begin(); it != player->_inventory.end(); ++it)
+ wealth += (*it)->_value;
+
+ snprintf(buf, 512, "Wealth: %d", wealth);
+ appendText(buf);
+
+ for (int i = 0; i < Chr::NUMBER_OF_ARMOR_TYPES; i++) {
+ if (player->_armor[i] != NULL) {
+ snprintf(buf, 512, "%s %s", armorMessages[i], player->_armor[i]->_name.c_str());
+ appendText(buf);
+ }
+ }
+
+ for (ObjArray::const_iterator it = player->_inventory.begin(); it != player->_inventory.end(); ++it) {
+ int uses = (*it)->_numberOfUses;
+
+ if (uses > 0) {
+ snprintf(buf, 512, "Your %s has %d uses left.", (*it)->_name.c_str(), uses);
+ appendText(buf);
+ }
+ }
+
+ printPlayerCondition(player);
+
+ _commandWasQuick = true;
+
+ return true;
+}
+
+bool WageEngine::handleRestCommand() {
+ if (getMonster() != NULL) {
+ appendText("This is no time to rest!");
+ _commandWasQuick = true;
+ } else {
+ regen();
+ printPlayerCondition(_world->_player);
+ }
+
+ return true;
+}
+
+bool WageEngine::handleAcceptCommand() {
+ Chr *chr = _offer->_currentOwner;
+
+ char buf[512];
+ snprintf(buf, 512, "%s%s lays the %s on the ground and departs peacefully.",
+ chr->getDefiniteArticle(true), chr->_name.c_str(), _offer->_name.c_str());
+ appendText(buf);
+
+ _world->move(_offer, chr->_currentScene);
+ _world->move(chr, _world->_storageScene);
+
+ return true;
+}
+
+bool WageEngine::handleTakeCommand(const char *target) {
+ Common::String t(target);
+ bool handled = false;
+
+ for (ObjList::const_iterator it = _world->_player->_currentScene->_objs.begin(); it != _world->_player->_currentScene->_objs.end(); ++it) {
+ Common::String n((*it)->_name);
+ n.toLowercase();
+
+ if (t.contains(n)) {
+ if ((*it)->_type == Obj::IMMOBILE_OBJECT) {
+ appendText("You can't move it.");
+ } else {
+ takeObj(*it);
+ }
+
+ handled = true;
+ break;
+ }
+ }
+
+ return handled;
+}
+
+bool WageEngine::handleDropCommand(const char *target) {
+ Common::String t(target);
+ bool handled = false;
+
+ t.toLowercase();
+
+ for (ObjArray::const_iterator it = _world->_player->_inventory.begin(); it != _world->_player->_inventory.end(); ++it) {
+ Common::String n((*it)->_name);
+ n.toLowercase();
+
+ if (t.contains(n)) {
+ char buf[256];
+
+ snprintf(buf, 256, "You no longer have the %s.", (*it)->_name.c_str());
+ appendText(buf);
+ _world->move(*it, _world->_player->_currentScene);
+
+ handled = true;
+ break;
+ }
+ }
+
+ return handled;
+}
+
+bool WageEngine::handleAimCommand(const char *t) {
+ bool wasHandled = true;
+ Common::String target(t);
+
+ target.toLowercase();
+
+ if (target.contains("head")) {
+ _aim = Chr::HEAD;
+ } else if (target.contains("chest")) {
+ _aim = Chr::CHEST;
+ } else if (target.contains("side")) {
+ _aim = Chr::SIDE;
+ } else {
+ wasHandled = false;
+ appendText("Please aim for the head, chest, or side.");
+ }
+
+ _commandWasQuick = true;
+
+ return wasHandled;
+}
+
+bool WageEngine::handleWearCommand(const char *t) {
+ Chr *player = _world->_player;
+ char buf[512];
+ Common::String target(t);
+ bool handled = false;
+
+ target.toLowercase();
+
+ for (ObjArray::const_iterator it = _world->_player->_inventory.begin(); it != _world->_player->_inventory.end(); ++it) {
+ Common::String n((*it)->_name);
+
+ if (target.contains(n)) {
+ if ((*it)->_type == Obj::HELMET) {
+ wearObj(*it, Chr::HEAD_ARMOR);
+ } else if ((*it)->_type == Obj::CHEST_ARMOR) {
+ wearObj(*it, Chr::BODY_ARMOR);
+ } else if ((*it)->_type == Obj::SHIELD) {
+ wearObj(*it, Chr::SHIELD_ARMOR);
+ } else if ((*it)->_type == Obj::SPIRITUAL_ARMOR) {
+ wearObj(*it, Chr::MAGIC_ARMOR);
+ } else {
+ appendText("You cannot wear that object.");
+ }
+
+ handled = true;
+ break;
+ }
+ }
+
+ for (ObjList::const_iterator it = player->_currentScene->_objs.begin(); it != player->_currentScene->_objs.end(); ++it) {
+ Common::String n((*it)->_name);
+ n.toLowercase();
+ if (target.contains(n)) {
+ snprintf(buf, 512, "First you must get the %s.", (*it)->_name.c_str());
+ appendText(buf);
+
+ handled = true;
+ break;
+ }
+ }
+
+ return handled;
+}
+
+void WageEngine::wearObj(Obj *o, int pos) {
+ Chr *player = _world->_player;
+ char buf[512];
+
+ if (player->_armor[pos] == o) {
+ snprintf(buf, 512, "You are already wearing the %s.", o->_name.c_str());
+ appendText(buf);
+ } else {
+ if (player->_armor[pos] != NULL) {
+ snprintf(buf, 512, "You are no longer wearing the %s.", player->_armor[pos]->_name.c_str());
+ appendText(buf);
+ }
+
+ player->_armor[pos] = o;
+ snprintf(buf, 512, "You are now wearing the %s.", o->_name.c_str());
+ appendText(buf);
+ }
+}
+
+
+bool WageEngine::handleOfferCommand(const char *target) {
+ Chr *player = _world->_player;
+ Chr *enemy = getMonster();
+
+ if (enemy != NULL) {
+ Common::String t(target);
+ t.toLowercase();
+
+ for (ObjArray::const_iterator it = player->_inventory.begin(); it != player->_inventory.end(); ++it) {
+ Common::String n((*it)->_name);
+ n.toLowercase();
+
+ if (t.contains(n)) {
+ if ((*it)->_value < enemy->_rejectsOffers) {
+ appendText("Your offer is rejected.");
+ } else {
+ appendText("Your offer is accepted.");
+ appendText(enemy->_acceptsOfferComment.c_str());
+ _world->move(*it, enemy);
+ _world->move(enemy, _world->_storageScene);
+ }
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool WageEngine::tryAttack(const Obj *weapon, const Common::String &input) {
+ Common::String w(weapon->_name);
+ w.toLowercase();
+ Common::String i(input);
+ i.toLowercase();
+ Common::String v(weapon->_operativeVerb);
+ v.toLowercase();
+
+ return i.contains(w) && i.contains(v);
+}
+
+bool WageEngine::handleAttack(Obj *weapon) {
+ Chr *player = _world->_player;
+ Chr *enemy = getMonster();
+
+ if (weapon->_type == Obj::MAGICAL_OBJECT) {
+ switch (weapon->_attackType) {
+ case Obj::HEALS_PHYSICAL_AND_SPIRITUAL_DAMAGE:
+ case Obj::HEALS_PHYSICAL_DAMAGE:
+ case Obj::HEALS_SPIRITUAL_DAMAGE:
+ performMagic(player, enemy, weapon);
+ return true;
+ }
+ }
+ if (enemy != NULL)
+ performAttack(player, enemy, weapon);
+ else if (weapon->_type == Obj::MAGICAL_OBJECT)
+ appendText("There is nobody to cast a spell at.");
+ else
+ appendText("There is no one to fight.");
+
+ return true;
+}
+
+const char *WageEngine::getPercentMessage(double percent) {
+ if (percent < 0.40) {
+ return "very bad";
+ } else if (percent < 0.55) {
+ return "bad";
+ } else if (percent < 0.70) {
+ return "average";
+ } else if (percent < 0.85) {
+ return "good";
+ } else if (percent <= 1.00) {
+ return "very good";
+ } else {
+ return "enhanced";
+ }
+}
+
+void WageEngine::printPlayerCondition(Chr *player) {
+ double physicalPercent = (double)player->_context._statVariables[PHYS_HIT_CUR] / player->_context._statVariables[PHYS_HIT_BAS];
+ double spiritualPercent = (double)player->_context._statVariables[SPIR_HIT_CUR] / player->_context._statVariables[SPIR_HIT_BAS];
+ char buf[256];
+
+ snprintf(buf, 256, "Your physical condition is %s.", getPercentMessage(physicalPercent));
+ appendText(buf);
+
+ snprintf(buf, 256, "Your spiritual condition is %s.", getPercentMessage(spiritualPercent));
+ appendText(buf);
+}
+
+} // End of namespace Wage
diff --git a/engines/wage/configure.engine b/engines/wage/configure.engine
new file mode 100644
index 0000000000..6205211158
--- /dev/null
+++ b/engines/wage/configure.engine
@@ -0,0 +1,3 @@
+# This file is included from the main "configure" script
+# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
+add_engine wage "WAGE" no
diff --git a/engines/wage/design.cpp b/engines/wage/design.cpp
new file mode 100644
index 0000000000..44891f1637
--- /dev/null
+++ b/engines/wage/design.cpp
@@ -0,0 +1,538 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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/primitives.h"
+#include "wage/wage.h"
+#include "wage/design.h"
+
+namespace Wage {
+
+struct PlotData {
+ Graphics::Surface *surface;
+ Patterns *patterns;
+ uint fillType;
+ int thickness;
+
+ PlotData(Graphics::Surface *s, Patterns *p, int f, int t) :
+ surface(s), patterns(p), fillType(f), thickness(t) {}
+};
+
+void drawPixel(int x, int y, int color, void *data);
+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;
+}
+
+Design::~Design() {
+ free(_data);
+ if (_surface)
+ _surface->free();
+ delete _surface;
+}
+
+void Design::paint(Graphics::Surface *surface, Patterns &patterns, int x, int y) {
+ Common::MemoryReadStream in(_data, _len);
+ Common::Rect r(0, 0, _bounds->width(), _bounds->height());
+ bool needRender = false;
+
+ if (_surface == NULL) {
+ _surface = new Graphics::Surface;
+ _surface->create(_bounds->width(), _bounds->height(), Graphics::PixelFormat::createFormatCLUT8());
+ _surface->fillRect(r, kColorGreen);
+
+ needRender = true;
+ }
+
+#if 0
+ PlotData pd(_surface, &patterns, 8, 1);
+ 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, kColorGray, 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
+
+ while (needRender) {
+ 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;
+ }
+
+ //g_system->copyRectToScreen(_surface->getPixels(), _surface->pitch, 0, 0, _surface->w, _surface->h);
+ //((WageEngine *)g_engine)->processEvents();
+ //g_system->updateScreen();
+ //g_system->delayMillis(500);
+ }
+
+ const int padding = 3;
+ for (int i = padding; i < _bounds->height() - 2 * padding; i++) {
+ const byte *src = (const byte *)_surface->getBasePtr(padding, i);
+ byte *dst = (byte *)surface->getBasePtr(x + padding, y+i);
+ for (int j = padding; j < _bounds->width() - 2 * padding; j++) {
+ if (*src != kColorGreen)
+ *dst = *src;
+ src++;
+ dst++;
+ }
+ }
+}
+
+bool Design::isPointOpaque(int x, int y) {
+ if (_surface == NULL)
+ error("Surface is null");
+
+ byte pixel = ((byte *)_surface->getBasePtr(x, y))[0];
+
+ return pixel != kColorGreen;
+}
+
+void drawPixel(int x, int y, int color, void *data) {
+ PlotData *p = (PlotData *)data;
+
+ if (p->fillType > p->patterns->size())
+ return;
+
+ 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 : 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 : kColorWhite;
+ }
+ }
+}
+
+void drawPixelPlain(int x, int y, int color, void *data) {
+ PlotData *p = (PlotData *)data;
+
+ 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::Surface *surface, Common::ReadStream &in,
+ Patterns &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);
+
+ Common::Rect r(x1, y1, x2, y2);
+ PlotData pd(surface, &patterns, fillType, 1);
+
+ if (fillType <= patterns.size())
+ Graphics::drawFilledRect(r, kColorBlack, drawPixel, &pd);
+
+ pd.fillType = borderFillType;
+ pd.thickness = borderThickness;
+
+ if (borderThickness > 0 && borderFillType <= patterns.size()) {
+ Graphics::drawLine(x1, y1, x2, y1, kColorBlack, drawPixel, &pd);
+ Graphics::drawLine(x2, y1, x2, y2, kColorBlack, drawPixel, &pd);
+ Graphics::drawLine(x2, y2, x1, y2, kColorBlack, drawPixel, &pd);
+ Graphics::drawLine(x1, y2, x1, y1, kColorBlack, drawPixel, &pd);
+ }
+}
+
+void Design::drawRoundRect(Graphics::Surface *surface, Common::ReadStream &in,
+ Patterns &patterns, byte fillType, byte borderThickness, byte borderFillType) {
+ int16 y1 = in.readSint16BE();
+ int16 x1 = in.readSint16BE();
+ int16 y2 = in.readSint16BE();
+ int16 x2 = in.readSint16BE();
+ int16 arc = in.readSint16BE();
+
+ if (x1 > x2)
+ SWAP(x1, x2);
+ if (y1 > y2)
+ SWAP(y1, y2);
+
+ Common::Rect r(x1, y1, x2, y2);
+ PlotData pd(surface, &patterns, fillType, 1);
+
+ if (fillType <= patterns.size())
+ Graphics::drawRoundRect(r, arc/2, kColorBlack, true, drawPixel, &pd);
+
+ pd.fillType = borderFillType;
+ pd.thickness = borderThickness;
+
+ if (borderThickness > 0 && borderFillType <= patterns.size())
+ Graphics::drawRoundRect(r, arc/2, kColorBlack, false, drawPixel, &pd);
+}
+
+void Design::drawPolygon(Graphics::Surface *surface, Common::ReadStream &in,
+ Patterns &patterns, byte fillType, byte borderThickness, byte borderFillType) {
+
+ byte ignored = in.readSint16BE(); // ignored
+ assert(ignored == 0);
+
+ 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);
+
+ 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);
+
+ 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);
+
+ if (fillType <= patterns.size()) {
+ Graphics::drawPolygonScan(xpoints, ypoints, npoints, bbox, kColorBlack, drawPixel, &pd);
+ }
+
+ pd.fillType = borderFillType;
+ pd.thickness = borderThickness;
+ if (borderThickness > 0 && borderFillType <= patterns.size()) {
+ for (int i = 1; i < npoints; i++)
+ Graphics::drawLine(xpoints[i-1], ypoints[i-1], xpoints[i], ypoints[i], kColorBlack, drawPixel, &pd);
+ }
+
+ free(xpoints);
+ free(ypoints);
+}
+
+void Design::drawOval(Graphics::Surface *surface, Common::ReadStream &in,
+ Patterns &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);
+
+ if (fillType <= patterns.size())
+ Graphics::drawEllipse(x1, y1, x2-1, y2-1, kColorBlack, true, drawPixel, &pd);
+
+ pd.fillType = borderFillType;
+ pd.thickness = borderThickness;
+
+ if (borderThickness > 0 && borderFillType <= patterns.size())
+ Graphics::drawEllipse(x1, y1, x2-1, y2-1, kColorBlack, false, drawPixel, &pd);
+}
+
+void Design::drawBitmap(Graphics::Surface *surface, Common::SeekableReadStream &in) {
+ int numBytes = in.readSint16BE();
+ int y1 = in.readSint16BE();
+ int x1 = in.readSint16BE();
+ int y2 = in.readSint16BE();
+ int x2 = in.readSint16BE();
+ int w = x2 - x1;
+ int h = y2 - y1;
+ 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;
+ 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;
+ if (state == 1) {
+ color = in.readByte();
+ numBytes--;
+ } else if (state == 2)
+ color = b;
+
+ for (int c = 0; c < 8; c++) {
+ 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;
+ }
+ }
+ }
+ }
+
+ in.skip(numBytes);
+
+ 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();
+
+ for (y = 0; y < h; y++) {
+ byte *src = (byte *)tmp.getBasePtr(0, y);
+ byte *dst = (byte *)surface->getBasePtr(x1, y1 + y);
+ for (x = 0; x < w; x++) {
+ if (*src != kColorGreen)
+ *dst = *src;
+ src++;
+ dst++;
+ }
+ }
+
+ tmp.free();
+}
+
+void Design::drawRect(Graphics::Surface *surface, Common::Rect &rect, int thickness, int color, Patterns &patterns, byte fillType) {
+ drawRect(surface, rect.left, rect.top, rect.right, rect.bottom, thickness, color, patterns, fillType);
+}
+
+void Design::drawRect(Graphics::Surface *surface, int x1, int y1, int x2, int y2, int thickness, int color, Patterns &patterns, byte fillType) {
+ PlotData pd(surface, &patterns, fillType, thickness);
+
+ Graphics::drawLine(x1, y1, x2, y1, kColorBlack, drawPixel, &pd);
+ Graphics::drawLine(x2, y1, x2, y2, kColorBlack, drawPixel, &pd);
+ Graphics::drawLine(x2, y2, x1, y2, kColorBlack, drawPixel, &pd);
+ Graphics::drawLine(x1, y2, x1, y1, kColorBlack, drawPixel, &pd);
+}
+
+
+void Design::drawFilledRect(Graphics::Surface *surface, Common::Rect &rect, int color, Patterns &patterns, byte fillType) {
+ PlotData pd(surface, &patterns, fillType, 1);
+
+ for (int y = rect.top; y <= rect.bottom; y++)
+ Graphics::drawHLine(rect.left, rect.right, y, color, drawPixel, &pd);
+}
+
+void Design::drawFilledRoundRect(Graphics::Surface *surface, Common::Rect &rect, int arc, int color, Patterns &patterns, byte fillType) {
+ PlotData pd(surface, &patterns, fillType, 1);
+
+ Graphics::drawRoundRect(rect, arc, color, true, drawPixel, &pd);
+}
+
+void Design::drawHLine(Graphics::Surface *surface, int x1, int x2, int y, int thickness, int color, Patterns &patterns, byte fillType) {
+ PlotData pd(surface, &patterns, fillType, thickness);
+
+ Graphics::drawHLine(x1, x2, y, color, drawPixel, &pd);
+}
+
+void Design::drawVLine(Graphics::Surface *surface, int x, int y1, int y2, int thickness, int color, Patterns &patterns, byte fillType) {
+ PlotData pd(surface, &patterns, fillType, thickness);
+
+ Graphics::drawVLine(x, y1, y2, color, drawPixel, &pd);
+}
+
+FloodFill::FloodFill(Graphics::Surface *surface, byte color1, byte color2) {
+ _surface = surface;
+ _color1 = color1;
+ _color2 = color2;
+ _w = surface->w;
+ _h = surface->h;
+
+ _visited = (byte *)calloc(_w * _h, 1);
+}
+
+FloodFill::~FloodFill() {
+ while(!_queue.empty()) {
+ Common::Point *p = _queue.front();
+
+ delete p;
+ _queue.pop_front();
+ }
+
+ free(_visited);
+}
+
+void FloodFill::addSeed(int x, int y) {
+ byte *p;
+
+ if (x >= 0 && x < _w && y >= 0 && y < _h) {
+ if (!_visited[y * _w + x] && *(p = (byte *)_surface->getBasePtr(x, y)) == _color1) {
+ _visited[y * _w + x] = 1;
+ *p = _color2;
+
+ Common::Point *pt = new Common::Point(x, y);
+
+ _queue.push_back(pt);
+ }
+ }
+}
+
+void FloodFill::fill() {
+ while (!_queue.empty()) {
+ Common::Point *p = _queue.front();
+ _queue.pop_front();
+ addSeed(p->x , p->y - 1);
+ addSeed(p->x - 1, p->y );
+ addSeed(p->x , p->y + 1);
+ addSeed(p->x + 1, p->y );
+
+ delete p;
+ }
+}
+
+
+} // End of namespace Wage
diff --git a/engines/wage/design.h b/engines/wage/design.h
new file mode 100644
index 0000000000..baa99730fe
--- /dev/null
+++ b/engines/wage/design.h
@@ -0,0 +1,115 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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 "graphics/surface.h"
+#include "common/memstream.h"
+#include "common/rect.h"
+
+namespace Wage {
+
+class Design {
+public:
+ Design(Common::SeekableReadStream *data);
+ ~Design();
+
+ void setBounds(Common::Rect *bounds) {
+ _bounds = bounds;
+ }
+
+ Common::Rect *getBounds() {
+ return _bounds;
+ }
+
+ void paint(Graphics::Surface *canvas, Patterns &patterns, int x, int y);
+ bool isPointOpaque(int x, int y);
+ static void drawRect(Graphics::Surface *surface, Common::Rect &rect, int thickness, int color, Patterns &patterns, byte fillType);
+ static void drawRect(Graphics::Surface *surface, int x1, int y1, int x2, int y2, int thickness, int color, Patterns &patterns, byte fillType);
+ static void drawFilledRect(Graphics::Surface *surface, Common::Rect &rect, int color, Patterns &patterns, byte fillType);
+ static void drawFilledRoundRect(Graphics::Surface *surface, Common::Rect &rect, int arc, int color, Patterns &patterns, byte fillType);
+ static void drawHLine(Graphics::Surface *surface, int x1, int x2, int y, int thickness, int color, Patterns &patterns, byte fillType);
+ static void drawVLine(Graphics::Surface *surface, int x, int y1, int y2, int thickness, int color, Patterns &patterns, byte fillType);
+
+
+private:
+ byte *_data;
+ int _len;
+ Common::Rect *_bounds;
+ Graphics::Surface *_surface;
+
+private:
+ void drawRect(Graphics::Surface *surface, Common::ReadStream &in,
+ Patterns &patterns, byte fillType, byte borderThickness, byte borderFillType);
+ void drawRoundRect(Graphics::Surface *surface, Common::ReadStream &in,
+ Patterns &patterns, byte fillType, byte borderThickness, byte borderFillType);
+ void drawPolygon(Graphics::Surface *surface, Common::ReadStream &in,
+ Patterns &patterns, byte fillType, byte borderThickness, byte borderFillType);
+ void drawOval(Graphics::Surface *surface, Common::ReadStream &in,
+ Patterns &patterns, byte fillType, byte borderThickness, byte borderFillType);
+ void drawBitmap(Graphics::Surface *surface, Common::SeekableReadStream &in);
+};
+
+class FloodFill {
+public:
+ FloodFill(Graphics::Surface *surface, byte color1, byte color2);
+ ~FloodFill();
+ void addSeed(int x, int y);
+ void fill();
+
+private:
+ Common::List<Common::Point *> _queue;
+ Graphics::Surface *_surface;
+ byte _color1, _color2;
+ byte *_visited;
+ int _w, _h;
+};
+
+} // End of namespace Wage
+
+#endif
diff --git a/engines/wage/detection.cpp b/engines/wage/detection.cpp
new file mode 100644
index 0000000000..e03c447484
--- /dev/null
+++ b/engines/wage/detection.cpp
@@ -0,0 +1,238 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+#include "base/plugins.h"
+
+#include "engines/advancedDetector.h"
+#include "common/system.h"
+#include "common/savefile.h"
+
+#include "wage/wage.h"
+
+namespace Wage {
+
+const char *WageEngine::getGameFile() const {
+ return _gameDescription->filesDescriptions[0].fileName;
+}
+
+}
+
+static const PlainGameDescriptor wageGames[] = {
+ {"afm", "Another Fine Mess"},
+ {"amot", "A Mess O' Trouble"},
+ {"cantitoe", "Camp Cantitoe"},
+ {"scepters", "Enchanted Scepters"},
+ {"wage", "WAGE"},
+ {0, 0}
+};
+
+namespace Wage {
+
+#define ADGF_DEFAULT (ADGF_DROPLANGUAGE|ADGF_DROPPLATFORM)
+#define ADGF_GENERIC (ADGF_DROPLANGUAGE|ADGF_DROPPLATFORM|ADGF_USEEXTRAASTITLE)
+
+static const ADGameDescription gameDescriptions[] = {
+ {
+ "afm",
+ "v1.8",
+ AD_ENTRY1s("Another Fine Mess 1.8", "8e5aa915f3253efb2aab52435647b25e", 1456000),
+ Common::EN_ANY,
+ Common::kPlatformMacintosh,
+ ADGF_DEFAULT,
+ GUIO0()
+ },
+ {
+ "amot",
+ "v1.8",
+ AD_ENTRY1s("A Mess O' Trouble 1.8", "b3ef53afed282671b704e45df829350c", 1895552),
+ Common::EN_ANY,
+ Common::kPlatformMacintosh,
+ ADGF_DEFAULT,
+ GUIO0()
+ },
+ {
+ "cantitoe",
+ "",
+ AD_ENTRY1s("Camp Cantitoe", "098aa5c11c58e1ef274a30a9e01b4755", 621440),
+ Common::EN_ANY,
+ Common::kPlatformMacintosh,
+ ADGF_DEFAULT,
+ GUIO0()
+ },
+ {
+ "wage",
+ "Deep Angst",
+ AD_ENTRY1s("Deep Angst", "635f62bbc569e72b03cab9107927d03d", 335232),
+ Common::EN_ANY,
+ Common::kPlatformMacintosh,
+ ADGF_GENERIC,
+ GUIO0()
+ },
+ {
+ "wage",
+ "Escape from School!",
+ AD_ENTRY1s("Escape from School!", "a854be48d4af20126d18a9cad93a969b", 51840),
+ Common::EN_ANY,
+ Common::kPlatformMacintosh,
+ ADGF_GENERIC,
+ GUIO0()
+ },
+ {
+ "wage",
+ "Queen Quest",
+ AD_ENTRY1s("Queen Quest", "730605d312efedb5e3ff108522fcac18", 59776),
+ Common::EN_ANY,
+ Common::kPlatformMacintosh,
+ ADGF_GENERIC,
+ GUIO0()
+ },
+ {
+ "scepters",
+ "",
+ AD_ENTRY1s("Scepters", "b80bff315897776dda7689cdf829fab4", 360832),
+ Common::EN_ANY,
+ Common::kPlatformMacintosh,
+ ADGF_DEFAULT,
+ GUIO0()
+ },
+ {
+ "wage",
+ "ZikTuria",
+ AD_ENTRY1s("ZikTuria", "e793155bed1a70fa2074a3fcd696b751", 54784),
+ Common::EN_ANY,
+ Common::kPlatformMacintosh,
+ ADGF_GENERIC,
+ GUIO0()
+ },
+ {
+ "wage",
+ "Zoony",
+ AD_ENTRY1s("Zoony", "e6cc8a914a4215dafbcce6315dd12cf5", 160256),
+ Common::EN_ANY,
+ Common::kPlatformMacintosh,
+ ADGF_GENERIC,
+ GUIO0()
+ },
+
+ AD_TABLE_END_MARKER
+};
+
+} // End of namespace Wage
+
+class WageMetaEngine : public AdvancedMetaEngine {
+public:
+ WageMetaEngine() : AdvancedMetaEngine(Wage::gameDescriptions, sizeof(ADGameDescription), wageGames) {
+ _singleid = "wage";
+ _guioptions = GUIO2(GUIO_NOSPEECH, GUIO_NOMIDI);
+ }
+
+ virtual const char *getName() const {
+ return "World Adventure Game Engine";
+ }
+
+ virtual const char *getOriginalCopyright() const {
+ return "World Builder (C) Silicon Beach Software";
+ }
+
+ virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const;
+ virtual bool hasFeature(MetaEngineFeature f) const;
+ virtual SaveStateList listSaves(const char *target) const;
+ virtual int getMaximumSaveSlot() const;
+ virtual void removeSaveState(const char *target, int slot) const;
+};
+
+bool WageMetaEngine::hasFeature(MetaEngineFeature f) const {
+ return
+ (f == kSupportsListSaves) ||
+ (f == kSupportsLoadingDuringStartup) ||
+ (f == kSupportsDeleteSave);
+}
+
+bool Wage::WageEngine::hasFeature(EngineFeature f) const {
+ return
+ (f == kSupportsRTL) ||
+ (f == kSupportsLoadingDuringRuntime) ||
+ (f == kSupportsSavingDuringRuntime);
+}
+
+bool WageMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
+ if (desc) {
+ *engine = new Wage::WageEngine(syst, desc);
+ }
+ return desc != 0;
+}
+
+SaveStateList WageMetaEngine::listSaves(const char *target) const {
+ const uint32 WAGEflag = MKTAG('W','A','G','E');
+ Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
+ Common::StringArray filenames;
+ char saveDesc[31];
+ Common::String pattern = target;
+ pattern += ".???";
+
+ filenames = saveFileMan->listSavefiles(pattern);
+ sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..)
+
+ SaveStateList saveList;
+ for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
+ // Obtain the last 3 digits of the filename, since they correspond to the save slot
+ int slotNum = atoi(file->c_str() + file->size() - 3);
+
+ if (slotNum >= 0 && slotNum <= 999) {
+ Common::InSaveFile *in = saveFileMan->openForLoading(*file);
+ if (in) {
+ uint32 type = in->readUint32BE();
+ if (type == WAGEflag)
+ in->read(saveDesc, 31);
+ saveList.push_back(SaveStateDescriptor(slotNum, saveDesc));
+ delete in;
+ }
+ }
+ }
+
+ return saveList;
+}
+
+int WageMetaEngine::getMaximumSaveSlot() const { return 999; }
+
+void WageMetaEngine::removeSaveState(const char *target, int slot) const {
+ g_system->getSavefileManager()->removeSavefile(Common::String::format("%s.%03d", target, slot));
+}
+
+#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() {
+ return false;
+}
+
+bool WageEngine::canSaveGameStateCurrently() {
+ return false;
+}
+
+} // End of namespace Wage
diff --git a/engines/wage/dialog.cpp b/engines/wage/dialog.cpp
new file mode 100644
index 0000000000..263570bddc
--- /dev/null
+++ b/engines/wage/dialog.cpp
@@ -0,0 +1,241 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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/system.h"
+#include "common/events.h"
+
+#include "wage/wage.h"
+#include "wage/design.h"
+#include "wage/gui.h"
+#include "wage/dialog.h"
+
+namespace Wage {
+
+enum {
+ kDialogHeight = 113
+};
+
+Dialog::Dialog(Gui *gui, int width, const char *text, DialogButtonArray *buttons, uint defaultButton) :
+ _gui(gui), _text(text), _buttons(buttons), _defaultButton(defaultButton) {
+ assert(_gui->_engine);
+ assert(_gui->_engine->_world);
+
+ _font = getDialogFont();
+
+ _tempSurface.create(width + 1, kDialogHeight + 1, Graphics::PixelFormat::createFormatCLUT8());
+
+ _bbox.left = (_gui->_screen.w - width) / 2;
+ _bbox.top = (_gui->_screen.h - kDialogHeight) / 2;
+ _bbox.right = (_gui->_screen.w + width) / 2;
+ _bbox.bottom = (_gui->_screen.h + kDialogHeight) / 2;
+
+ _pressedButton = -1;
+
+ _mouseOverPressedButton = false;
+
+ // Adjust button positions
+ for (uint i = 0; i < _buttons->size(); i++)
+ _buttons->operator[](i)->bounds.translate(_bbox.left, _bbox.top);
+
+ _needsRedraw = true;
+}
+
+Dialog::~Dialog() {
+}
+
+const Graphics::Font *Dialog::getDialogFont() {
+ return _gui->getFont("Chicago-12", Graphics::FontManager::kBigGUIFont);
+}
+
+void Dialog::paint() {
+ Design::drawFilledRect(&_gui->_screen, _bbox, kColorWhite, _gui->_patterns, kPatternSolid);
+ _font->drawString(&_gui->_screen, _text, _bbox.left + 24, _bbox.top + 16, _bbox.width(), kColorBlack);
+
+ static int boxOutline[] = { 1, 0, 0, 1, 1 };
+ drawOutline(_bbox, boxOutline, ARRAYSIZE(boxOutline));
+
+ for (uint i = 0; i < _buttons->size(); i++) {
+ DialogButton *button = _buttons->operator[](i);
+ static int buttonOutline[] = { 0, 0, 0, 0, 1 };
+
+ if (i == _defaultButton) {
+ buttonOutline[0] = buttonOutline[1] = 1;
+ } else {
+ buttonOutline[0] = buttonOutline[1] = 0;
+ }
+
+ int color = kColorBlack;
+
+ if ((int)i == _pressedButton && _mouseOverPressedButton) {
+ Common::Rect bb(button->bounds.left + 5, button->bounds.top + 5,
+ button->bounds.right - 5, button->bounds.bottom - 5);
+
+ Design::drawFilledRect(&_gui->_screen, bb, kColorBlack, _gui->_patterns, kPatternSolid);
+
+ color = kColorWhite;
+ }
+ int w = _font->getStringWidth(button->text);
+ int x = button->bounds.left + (button->bounds.width() - w) / 2;
+ int y = button->bounds.top + 6;
+
+ _font->drawString(&_gui->_screen, button->text, x, y, _bbox.width(), color);
+
+ drawOutline(button->bounds, buttonOutline, ARRAYSIZE(buttonOutline));
+ }
+
+ g_system->copyRectToScreen(_gui->_screen.getBasePtr(_bbox.left, _bbox.top), _gui->_screen.pitch,
+ _bbox.left, _bbox.top, _bbox.width() + 1, _bbox.height() + 1);
+
+ _needsRedraw = false;
+}
+
+void Dialog::drawOutline(Common::Rect &bounds, int *spec, int speclen) {
+ for (int i = 0; i < speclen; i++)
+ if (spec[i] != 0)
+ Design::drawRect(&_gui->_screen, bounds.left + i, bounds.top + i, bounds.right - i, bounds.bottom - i,
+ 1, kColorBlack, _gui->_patterns, kPatternSolid);
+}
+
+int Dialog::run() {
+ bool shouldQuit = false;
+ Common::Rect r(_bbox);
+
+ _tempSurface.copyRectToSurface(_gui->_screen.getBasePtr(_bbox.left, _bbox.top), _gui->_screen.pitch, 0, 0, _bbox.width() + 1, _bbox.height() + 1);
+ _gui->pushArrowCursor();
+
+ while (!shouldQuit) {
+ Common::Event event;
+
+ while (_gui->_engine->_eventMan->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_QUIT:
+ _gui->_engine->_shouldQuit = true;
+ shouldQuit = true;
+ break;
+ case Common::EVENT_MOUSEMOVE:
+ mouseMove(event.mouse.x, event.mouse.y);
+ break;
+ case Common::EVENT_LBUTTONDOWN:
+ mouseClick(event.mouse.x, event.mouse.y);
+ break;
+ case Common::EVENT_LBUTTONUP:
+ shouldQuit = mouseRaise(event.mouse.x, event.mouse.y);
+ break;
+ case Common::EVENT_KEYDOWN:
+ switch (event.kbd.keycode) {
+ case Common::KEYCODE_ESCAPE:
+ _pressedButton = -1;
+ shouldQuit = true;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (_needsRedraw)
+ paint();
+
+ g_system->updateScreen();
+ g_system->delayMillis(50);
+ }
+
+ _gui->_screen.copyRectToSurface(_tempSurface.getBasePtr(0, 0), _tempSurface.pitch, _bbox.left, _bbox.top, _bbox.width() + 1, _bbox.height() + 1);
+ g_system->copyRectToScreen(_gui->_screen.getBasePtr(r.left, r.top), _gui->_screen.pitch, r.left, r.top, r.width() + 1, r.height() + 1);
+
+ _gui->popCursor();
+
+ return _pressedButton;
+}
+
+int Dialog::matchButton(int x, int y) {
+ for (uint i = 0; i < _buttons->size(); i++)
+ if (_buttons->operator[](i)->bounds.contains(x, y))
+ return i;
+
+ return -1;
+}
+
+void Dialog::mouseMove(int x, int y) {
+ if (_pressedButton != -1) {
+ int match = matchButton(x, y);
+
+ if (_mouseOverPressedButton && match != _pressedButton) {
+ _mouseOverPressedButton = false;
+ _needsRedraw = true;
+ } else if (!_mouseOverPressedButton && match == _pressedButton) {
+ _mouseOverPressedButton = true;
+ _needsRedraw = true;
+ }
+ }
+}
+
+void Dialog::mouseClick(int x, int y) {
+ int match = matchButton(x, y);
+
+ if (match != -1) {
+ _pressedButton = match;
+ _mouseOverPressedButton = true;
+
+ _needsRedraw = true;
+ }
+}
+
+int Dialog::mouseRaise(int x, int y) {
+ bool res = false;
+
+ if (_pressedButton != -1) {
+ if (matchButton(x, y) == _pressedButton)
+ res = true;
+ }
+
+ return res;
+}
+
+} // End of namespace Wage
diff --git a/engines/wage/dialog.h b/engines/wage/dialog.h
new file mode 100644
index 0000000000..c5878acc95
--- /dev/null
+++ b/engines/wage/dialog.h
@@ -0,0 +1,101 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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_DIALOG_H
+#define WAGE_DIALOG_H
+
+namespace Wage {
+
+struct DialogButton {
+ Common::String text;
+ Common::Rect bounds;
+
+ DialogButton(const char *t, int x1, int y1, int w, int h) {
+ text = t;
+ bounds.left = x1;
+ bounds.top = y1;
+ bounds.right = x1 + w - 1;
+ bounds.bottom = y1 + h - 1;
+ }
+};
+
+typedef Common::Array<DialogButton *> DialogButtonArray;
+
+class Dialog {
+public:
+ Dialog(Gui *gui, int width, const char *text, DialogButtonArray *buttons, uint defaultButton);
+ ~Dialog();
+
+ int run();
+
+private:
+ Gui *_gui;
+ Graphics::Surface _tempSurface;
+ Common::Rect _bbox;
+ Common::String _text;
+
+ const Graphics::Font *_font;
+ DialogButtonArray *_buttons;
+ int _pressedButton;
+ uint _defaultButton;
+ bool _mouseOverPressedButton;
+
+ bool _needsRedraw;
+
+private:
+ const Graphics::Font *getDialogFont();
+ void drawOutline(Common::Rect &bounds, int *spec, int speclen);
+ void paint();
+ void mouseMove(int x, int y);
+ void mouseClick(int x, int y);
+ int mouseRaise(int x, int y);
+ int matchButton(int x, int y);
+};
+
+} // End of namespace Wage
+
+#endif
diff --git a/engines/wage/entities.cpp b/engines/wage/entities.cpp
new file mode 100644
index 0000000000..f85a228fbd
--- /dev/null
+++ b/engines/wage/entities.cpp
@@ -0,0 +1,500 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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/script.h"
+#include "wage/world.h"
+
+#include "common/memstream.h"
+
+namespace Wage {
+
+void Designed::setDesignBounds(Common::Rect *bounds) {
+ _designBounds = bounds;
+ _design->setBounds(bounds);
+}
+
+Designed::~Designed() {
+ delete _design;
+ delete _designBounds;
+}
+
+Context::Context() {
+ _visits = 0;
+ _kills = 0;
+ _experience = 0;
+ _frozen = false;
+
+ for (int i = 0; i < 26 * 9; i++)
+ _userVariables[i] = 0;
+
+ for (int i = 0; i < 18; i++)
+ _statVariables[i] = 0;
+}
+
+Scene::Scene() {
+ _script = NULL;
+ _design = NULL;
+ _textBounds = NULL;
+ _fontSize = 0;
+ _fontType = 0;
+
+ 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) {
+ _name = name;
+ _classType = SCENE;
+ _design = new Design(data);
+
+ _script = NULL;
+ _textBounds = NULL;
+ _fontSize = 0;
+ _fontType = 0;
+
+ 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] = readPascalString(data);
+ _messages[SOUTH] = readPascalString(data);
+ _messages[EAST] = readPascalString(data);
+ _messages[WEST] = readPascalString(data);
+ _soundName = readPascalString(data);
+
+ _visited = false;
+
+ delete data;
+}
+
+Scene::~Scene() {
+ delete _script;
+ delete _textBounds;
+}
+
+void Scene::paint(Graphics::Surface *surface, int x, int y) {
+ Common::Rect r(x + 5, y + 5, _design->getBounds()->width() + x - 10, _design->getBounds()->height() + y - 10);
+ 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, "paining Obj: %s", (*it)->_name.c_str());
+ (*it)->_design->paint(surface, ((WageEngine *)g_engine)->_world->_patterns, x, y);
+ }
+
+ for (ChrList::const_iterator it = _chrs.begin(); it != _chrs.end(); ++it) {
+ debug(2, "paining Chr: %s", (*it)->_name.c_str());
+ (*it)->_design->paint(surface, ((WageEngine *)g_engine)->_world->_patterns, x, y);
+ }
+}
+
+// Source: Apple IIGS Technical Note #41, "Font Family Numbers"
+// http://apple2.boldt.ca/?page=til/tn.iigs.041
+static const char *const fontNames[] = {
+ "Chicago", // system font
+ "Geneva", // application font
+ "New York",
+ "Geneva",
+
+ "Monaco",
+ "Venice",
+ "London",
+ "Athens",
+
+ "San Francisco",
+ "Toronto",
+ NULL,
+ "Cairo",
+ "Los Angeles", // 12
+
+ "Zapf Dingbats",
+ "Bookman",
+ "Helvetica Narrow",
+ "Palatino",
+ NULL,
+ "Zapf Chancery",
+ NULL,
+
+ "Times", // 20
+ "Helvetica",
+ "Courier",
+ "Symbol",
+ "Taliesin", // mobile?
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, // 30
+ NULL,
+ NULL,
+ "Avant Garde",
+ "New Century Schoolbook"
+};
+
+const char *Scene::getFontName() {
+ if (_fontType >= 0 && _fontType < ARRAYSIZE(fontNames) && fontNames[_fontType] != NULL) {
+ return fontNames[_fontType];
+ }
+ return "Unknown";
+}
+
+Obj::Obj(Common::String name, Common::SeekableReadStream *data) {
+ _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 = readPascalString(data);
+ _clickMessage = readPascalString(data);
+ _operativeVerb = readPascalString(data);
+ _failureMessage = readPascalString(data);
+ _useMessage = readPascalString(data);
+ _sound = readPascalString(data);
+
+ 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) {
+ warning("STUB: Obj::resetState()");
+}
+
+Chr::Chr(Common::String name, Common::SeekableReadStream *data) {
+ _name = name;
+ _classType = CHR;
+ _design = new Design(data);
+
+ _index = 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 = readPascalString(data);
+ _nativeWeapon1 = readPascalString(data);
+ _operativeVerb1 = readPascalString(data);
+ _nativeWeapon2 = readPascalString(data);
+ _operativeVerb2 = readPascalString(data);
+
+ _initialComment = readPascalString(data);
+ _scoresHitComment = readPascalString(data);
+ _receivesHitComment = readPascalString(data);
+ _makesOfferComment = readPascalString(data);
+ _rejectsOfferComment = readPascalString(data);
+ _acceptsOfferComment = readPascalString(data);
+ _dyingWords = readPascalString(data);
+
+ _initialSound = readPascalString(data);
+ _scoresHitSound = readPascalString(data);
+ _receivesHitSound = readPascalString(data);
+ _dyingSound = readPascalString(data);
+
+ _weaponSound1 = readPascalString(data);
+ _weaponSound2 = readPascalString(data);
+
+ 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;
+}
+
+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
diff --git a/engines/wage/entities.h b/engines/wage/entities.h
new file mode 100644
index 0000000000..0f5016e8b1
--- /dev/null
+++ b/engines/wage/entities.h
@@ -0,0 +1,345 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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 {
+ struct Surface;
+}
+
+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 _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() { 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);
+
+ int _index;
+ 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() : _currentOwner(NULL), _currentScene(NULL) {}
+ Obj(Common::String name, Common::SeekableReadStream *data);
+ ~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;
+ 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
+ };
+
+ Script *_script;
+ Common::String _text;
+ Common::Rect *_textBounds;
+ int _fontSize;
+ int _fontType; // 3 => Geneva, 22 => Courier, param to TextFont() function
+ 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();
+
+ Common::Rect *getTextBounds() {
+ return _textBounds == NULL ? NULL : new Common::Rect(*_textBounds);
+ }
+
+ void paint(Graphics::Surface *screen, int x, int y);
+
+ const char *getFontName();
+};
+
+class Sound {
+public:
+ Sound(Common::String name, Common::SeekableReadStream *data) : _name(name), _data(data) {}
+ ~Sound() { }
+
+ Common::String _name;
+ Common::SeekableReadStream *_data;
+};
+
+} // End of namespace Wage
+
+#endif
diff --git a/engines/wage/gui-console.cpp b/engines/wage/gui-console.cpp
new file mode 100644
index 0000000000..f1095de3ca
--- /dev/null
+++ b/engines/wage/gui-console.cpp
@@ -0,0 +1,398 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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/unzip.h"
+#include "graphics/cursorman.h"
+#include "graphics/fonts/bdf.h"
+#include "graphics/palette.h"
+
+#include "wage/wage.h"
+#include "wage/design.h"
+#include "wage/entities.h"
+#include "wage/menu.h"
+#include "wage/gui.h"
+#include "wage/world.h"
+
+namespace Wage {
+
+const Graphics::Font *Gui::getConsoleFont() {
+ char fontName[128];
+ Scene *scene = _engine->_world->_player->_currentScene;
+
+ snprintf(fontName, 128, "%s-%d", scene->getFontName(), scene->_fontSize);
+
+ return getFont(fontName, Graphics::FontManager::kConsoleFont);
+}
+
+void Gui::clearOutput() {
+ _out.clear();
+ _lines.clear();
+ _consoleFullRedraw = true;
+}
+
+void Gui::appendText(const char *s) {
+ Common::String str(s);
+ _consoleDirty = true;
+
+ if (!str.contains('\n')) {
+ _out.push_back(str);
+ flowText(str);
+ return;
+ }
+
+ // Okay, we got new lines, need to split it
+ // and push substrings individually
+ Common::String tmp;
+
+ for (uint i = 0; i < str.size(); i++) {
+ if (str[i] == '\n') {
+ _out.push_back(tmp);
+ flowText(tmp);
+ tmp.clear();
+ continue;
+ }
+
+ tmp += str[i];
+ }
+
+ _out.push_back(tmp);
+ flowText(tmp);
+}
+
+enum {
+ kConWOverlap = 20,
+ kConHOverlap = 20,
+ kConWPadding = 3,
+ kConHPadding = 4,
+ kConOverscan = 3
+};
+
+void Gui::flowText(Common::String &str) {
+ Common::StringArray wrappedLines;
+ int textW = _consoleTextArea.width() - kConWPadding * 2;
+ const Graphics::Font *font = getConsoleFont();
+
+ font->wordWrapText(str, textW, wrappedLines);
+
+ if (wrappedLines.empty()) // Sometimes we have empty lines
+ _lines.push_back("");
+
+ for (Common::StringArray::const_iterator j = wrappedLines.begin(); j != wrappedLines.end(); ++j)
+ _lines.push_back(*j);
+
+ uint pos = _scrollPos;
+ _scrollPos = MAX<int>(0, (_lines.size() - 1 - _consoleNumLines) * _consoleLineHeight);
+
+ _cursorX = kConWPadding;
+
+ if (_scrollPos)
+ _cursorY = (_consoleNumLines) * _consoleLineHeight + kConHPadding;
+ else
+ _cursorY = (_lines.size() - 1) * _consoleLineHeight + kConHPadding;
+
+ if (pos != _scrollPos)
+ _consoleFullRedraw = true;
+
+ if (!_engine->_temporarilyHidden)
+ draw();
+}
+
+void Gui::renderConsole(Graphics::Surface *g, Common::Rect &r) {
+ bool fullRedraw = _consoleFullRedraw;
+ bool textReflow = false;
+ int surfW = r.width() + kConWOverlap * 2;
+ int surfH = r.height() + kConHOverlap * 2;
+
+ Common::Rect boundsR(kConWOverlap - kConOverscan, kConHOverlap - kConOverscan,
+ r.width() + kConWOverlap + kConOverscan, r.height() + kConHOverlap + kConOverscan);
+ Common::Rect fullR(0, 0, surfW, surfH);
+
+ if (_console.w != surfW || _console.h != surfH) {
+ if (_console.w != surfW)
+ textReflow = true;
+
+ _console.free();
+
+ _console.create(surfW, surfH, Graphics::PixelFormat::createFormatCLUT8());
+ fullRedraw = true;
+ }
+
+ if (fullRedraw)
+ _console.fillRect(fullR, kColorWhite);
+
+ const Graphics::Font *font = getConsoleFont();
+
+ _consoleLineHeight = font->getFontHeight();
+ int textW = r.width() - kConWPadding * 2;
+ int textH = r.height() - kConHPadding * 2;
+
+ if (textReflow) {
+ _lines.clear();
+
+ for (uint i = 0; i < _out.size(); i++)
+ flowText(_out[i]);
+ }
+
+ const int firstLine = _scrollPos / _consoleLineHeight;
+ const int lastLine = MIN((_scrollPos + textH) / _consoleLineHeight + 1, _lines.size());
+ const int xOff = kConWOverlap;
+ const int yOff = kConHOverlap;
+ int x1 = xOff + kConWPadding;
+ int y1 = yOff - (_scrollPos % _consoleLineHeight) + kConHPadding;
+
+ if (fullRedraw)
+ _consoleNumLines = (r.height() - 2 * kConWPadding) / _consoleLineHeight - 2;
+
+ for (int line = firstLine; line < lastLine; line++) {
+ const char *str = _lines[line].c_str();
+ int color = kColorBlack;
+
+ if ((line > _selectionStartY && line < _selectionEndY) ||
+ (line > _selectionEndY && line < _selectionStartY)) {
+ color = kColorWhite;
+ Common::Rect trect(0, y1, _console.w, y1 + _consoleLineHeight);
+
+ Design::drawFilledRect(&_console, trect, kColorBlack, _patterns, kPatternSolid);
+ }
+
+ if (line == _selectionStartY || line == _selectionEndY) {
+ if (_selectionStartY != _selectionEndY) {
+ int color1 = kColorBlack;
+ int color2 = kColorWhite;
+ int midpoint = _selectionStartX;
+
+ if (_selectionStartY > _selectionEndY)
+ SWAP(color1, color2);
+
+ if (line == _selectionEndY) {
+ SWAP(color1, color2);
+ midpoint = _selectionEndX;
+ }
+
+ Common::String beg(_lines[line].c_str(), &_lines[line].c_str()[midpoint]);
+ Common::String end(&_lines[line].c_str()[midpoint]);
+
+ int rectW = font->getStringWidth(beg) + kConWPadding + kConWOverlap;
+ Common::Rect trect(0, y1, _console.w, y1 + _consoleLineHeight);
+ if (color1 == kColorWhite)
+ trect.right = rectW;
+ else
+ trect.left = rectW;
+
+ Design::drawFilledRect(&_console, trect, kColorBlack, _patterns, kPatternSolid);
+
+ font->drawString(&_console, beg, x1, y1, textW, color1);
+ font->drawString(&_console, end, x1 + rectW - kConWPadding - kConWOverlap, y1, textW, color2);
+ } else {
+ int startPos = _selectionStartX;
+ int endPos = _selectionEndX;
+
+ if (startPos > endPos)
+ SWAP(startPos, endPos);
+
+ Common::String beg(_lines[line].c_str(), &_lines[line].c_str()[startPos]);
+ Common::String mid(&_lines[line].c_str()[startPos], &_lines[line].c_str()[endPos]);
+ Common::String end(&_lines[line].c_str()[endPos]);
+
+ int rectW1 = font->getStringWidth(beg) + kConWPadding + kConWOverlap;
+ int rectW2 = rectW1 + font->getStringWidth(mid);
+ Common::Rect trect(rectW1, y1, rectW2, y1 + _consoleLineHeight);
+
+ Design::drawFilledRect(&_console, trect, kColorBlack, _patterns, kPatternSolid);
+
+ font->drawString(&_console, beg, x1, y1, textW, kColorBlack);
+ font->drawString(&_console, mid, x1 + rectW1 - kConWPadding - kConWOverlap, y1, textW, kColorWhite);
+ font->drawString(&_console, end, x1 + rectW2 - kConWPadding - kConWOverlap, y1, textW, kColorBlack);
+ }
+ } else {
+ if (*str)
+ font->drawString(&_console, _lines[line], x1, y1, textW, color);
+ }
+
+ y1 += _consoleLineHeight;
+ }
+
+ g->copyRectToSurface(_console, r.left - kConOverscan, r.top - kConOverscan, boundsR);
+ g_system->copyRectToScreen(g->getBasePtr(r.left, r.top), g->pitch, r.left, r.top, r.width(), r.height());
+}
+
+void Gui::drawInput() {
+ if (!_screen.getPixels())
+ return;
+
+ if (_sceneIsActive) {
+ _sceneIsActive = false;
+ _bordersDirty = true;
+ }
+
+ _out.pop_back();
+ _lines.pop_back();
+ appendText(_engine->_inputText.c_str());
+ _inputTextLineNum = _out.size() - 1;
+
+ const Graphics::Font *font = getConsoleFont();
+
+ if (_engine->_inputText.contains('\n')) {
+ _consoleDirty = true;
+ } else {
+ int x = kConWPadding + _consoleTextArea.left;
+ int y = _cursorY + _consoleTextArea.top;
+
+ Common::Rect r(x, y, x + _consoleTextArea.width() - kConWPadding, y + font->getFontHeight());
+ _screen.fillRect(r, kColorWhite);
+
+ undrawCursor();
+
+ font->drawString(&_screen, _out[_inputTextLineNum], x, y, _screen.w, kColorBlack);
+
+ g_system->copyRectToScreen(_screen.getBasePtr(x, y), _screen.pitch, x, y, _consoleTextArea.width(), font->getFontHeight());
+ }
+
+ _cursorX = font->getStringWidth(_out[_inputTextLineNum]) + kConHPadding;
+}
+
+void Gui::actionCopy() {
+ if (_selectionStartX == -1)
+ return;
+
+ int startX = _selectionStartX;
+ int startY = _selectionStartY;
+ int endX = _selectionEndX;
+ int endY = _selectionEndY;
+
+ if (startY > endY) {
+ SWAP(startX, endX);
+ SWAP(endX, endY);
+ }
+
+ _clipboard.clear();
+
+ for (int i = startY; i <= endY; i++) {
+ if (startY == endY) {
+ _clipboard = Common::String(&_lines[i].c_str()[startX], &_lines[i].c_str()[endX]);
+ break;
+ }
+
+ if (i == startY) {
+ _clipboard += &_lines[i].c_str()[startX];
+ _clipboard += '\n';
+ } else if (i == endY) {
+ _clipboard += Common::String(_lines[i].c_str(), &_lines[i].c_str()[endX]);
+ } else {
+ _clipboard += _lines[i];
+ _clipboard += '\n';
+ }
+ }
+
+ _menu->enableCommand(kMenuEdit, kMenuActionPaste, true);
+}
+
+void Gui::actionPaste() {
+ _undobuffer = _engine->_inputText;
+ _engine->_inputText += _clipboard;
+ drawInput();
+ _engine->_inputText = _out.back(); // Set last part of the multiline text
+
+ _menu->enableCommand(kMenuEdit, kMenuActionUndo, true);
+}
+
+void Gui::actionUndo() {
+ _engine->_inputText = _undobuffer;
+ drawInput();
+
+ _menu->enableCommand(kMenuEdit, kMenuActionUndo, false);
+}
+
+void Gui::actionClear() {
+ int startPos = _selectionStartX;
+ int endPos = _selectionEndX;
+
+ if (startPos > endPos)
+ SWAP(startPos, endPos);
+
+ Common::String beg(_lines[_selectionStartY].c_str(), &_lines[_selectionStartY].c_str()[startPos]);
+ Common::String end(&_lines[_selectionStartY].c_str()[endPos]);
+
+ _undobuffer = _engine->_inputText;
+ _engine->_inputText = beg + end;
+ drawInput();
+
+ _menu->enableCommand(kMenuEdit, kMenuActionUndo, true);
+
+ _selectionStartY = -1;
+ _selectionEndY = -1;
+}
+
+void Gui::actionCut() {
+ int startPos = _selectionStartX;
+ int endPos = _selectionEndX;
+
+ if (startPos > endPos)
+ SWAP(startPos, endPos);
+
+ Common::String beg(_lines[_selectionStartY].c_str(), &_lines[_selectionStartY].c_str()[startPos]);
+ Common::String mid(&_lines[_selectionStartY].c_str()[startPos], &_lines[_selectionStartY].c_str()[endPos]);
+ Common::String end(&_lines[_selectionStartY].c_str()[endPos]);
+
+ _undobuffer = _engine->_inputText;
+ _engine->_inputText = beg + end;
+ _clipboard = mid;
+ drawInput();
+
+ _menu->enableCommand(kMenuEdit, kMenuActionUndo, true);
+ _menu->enableCommand(kMenuEdit, kMenuActionPaste, true);
+
+ _selectionStartY = -1;
+ _selectionEndY = -1;
+}
+
+void Gui::disableUndo() {
+ _menu->enableCommand(kMenuEdit, kMenuActionUndo, false);
+}
+
+} // End of namespace Wage
diff --git a/engines/wage/gui.cpp b/engines/wage/gui.cpp
new file mode 100644
index 0000000000..f7196abf17
--- /dev/null
+++ b/engines/wage/gui.cpp
@@ -0,0 +1,594 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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/unzip.h"
+#include "graphics/cursorman.h"
+#include "graphics/fonts/bdf.h"
+#include "graphics/palette.h"
+
+#include "wage/wage.h"
+#include "wage/design.h"
+#include "wage/entities.h"
+#include "wage/menu.h"
+#include "wage/gui.h"
+#include "wage/world.h"
+
+namespace Wage {
+
+static const byte palette[] = {
+ 0, 0, 0, // Black
+ 0x80, 0x80, 0x80, // Gray
+ 0xff, 0xff, 0xff, // White
+ 0x00, 0xff, 0x00 // Green
+};
+
+static byte fillPatterns[][8] = { { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, // kPatternSolid
+ { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 }, // kPatternStripes
+ { 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55 }, // kPatternCheckers
+ { 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa } // kPatternCheckers2
+};
+
+static const byte macCursorArrow[] = {
+ 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 2, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3,
+ 2, 0, 0, 2, 3, 3, 3, 3, 3, 3, 3,
+ 2, 0, 0, 0, 2, 3, 3, 3, 3, 3, 3,
+ 2, 0, 0, 0, 0, 2, 3, 3, 3, 3, 3,
+ 2, 0, 0, 0, 0, 0, 2, 3, 3, 3, 3,
+ 2, 0, 0, 0, 0, 0, 0, 2, 3, 3, 3,
+ 2, 0, 0, 0, 0, 0, 0, 0, 2, 3, 3,
+ 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3,
+ 2, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2,
+ 2, 0, 0, 2, 0, 0, 2, 3, 3, 3, 3,
+ 2, 0, 2, 3, 2, 0, 0, 2, 3, 3, 3,
+ 2, 2, 3, 3, 2, 0, 0, 2, 3, 3, 3,
+ 2, 3, 3, 3, 3, 2, 0, 0, 2, 3, 3,
+ 3, 3, 3, 3, 3, 2, 0, 0, 2, 3, 3,
+ 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3
+};
+
+static const byte macCursorBeam[] = {
+ 0, 0, 3, 3, 3, 0, 0, 3, 3, 3, 3,
+ 3, 3, 0, 3, 0, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 0, 3, 0, 3, 3, 3, 3, 3, 3,
+ 0, 0, 3, 3, 3, 0, 0, 3, 3, 3, 3,
+};
+
+static void cursorTimerHandler(void *refCon) {
+ Gui *gui = (Gui *)refCon;
+
+ int x = gui->_cursorX;
+ int y = gui->_cursorY;
+
+ if (x == 0 && y == 0)
+ return;
+
+ if (!gui->_screen.getPixels())
+ return;
+
+ x += gui->_consoleTextArea.left;
+ y += gui->_consoleTextArea.top;
+
+ gui->_screen.vLine(x, y, y + kCursorHeight, gui->_cursorState ? kColorBlack : kColorWhite);
+
+ if (!gui->_cursorOff)
+ gui->_cursorState = !gui->_cursorState;
+
+ gui->_cursorRect.left = x;
+ gui->_cursorRect.right = x + 1;
+ gui->_cursorRect.top = y;
+ gui->_cursorRect.bottom = y + kCursorHeight;
+
+ gui->_cursorDirty = true;
+}
+
+Gui::Gui(WageEngine *engine) {
+ _engine = engine;
+ _scene = NULL;
+ _sceneDirty = true;
+ _consoleDirty = true;
+ _bordersDirty = true;
+ _menuDirty = true;
+ _cursorDirty = false;
+ _consoleFullRedraw = true;
+ _screen.create(g_system->getWidth(), g_system->getHeight(), Graphics::PixelFormat::createFormatCLUT8());
+
+ _scrollPos = 0;
+ _consoleLineHeight = 8; // Dummy value which makes sense
+ _consoleNumLines = 24; // Dummy value
+ _builtInFonts = false;
+ _sceneIsActive = false;
+
+ _cursorX = 0;
+ _cursorY = 0;
+ _cursorState = false;
+ _cursorOff = false;
+
+ _inTextSelection = false;
+ _selectionStartX = _selectionStartY = -1;
+ _selectionEndX = _selectionEndY = -1;
+
+ _inputTextLineNum = 0;
+
+ g_system->getPaletteManager()->setPalette(palette, 0, 4);
+
+ CursorMan.replaceCursorPalette(palette, 0, 4);
+ CursorMan.replaceCursor(macCursorArrow, 11, 16, 1, 1, 3);
+ _cursorIsArrow = true;
+ CursorMan.showMouse(true);
+
+ for (int i = 0; i < ARRAYSIZE(fillPatterns); i++)
+ _patterns.push_back(fillPatterns[i]);
+
+ loadFonts();
+
+ g_system->getTimerManager()->installTimerProc(&cursorTimerHandler, 200000, this, "wageCursor");
+
+ _menu = new Menu(this);
+}
+
+Gui::~Gui() {
+ _screen.free();
+ _console.free();
+ g_system->getTimerManager()->removeTimerProc(&cursorTimerHandler);
+ delete _menu;
+}
+
+void Gui::undrawCursor() {
+ _cursorOff = true;
+ _cursorState = false;
+ cursorTimerHandler(this);
+ _cursorOff = false;
+}
+
+const Graphics::Font *Gui::getFont(const char *name, Graphics::FontManager::FontUsage fallback) {
+ const Graphics::Font *font;
+
+ if (!_builtInFonts) {
+ font = FontMan.getFontByName(name);
+
+ if (!font)
+ warning("Cannot load font %s", name);
+ }
+
+ if (_builtInFonts || !font)
+ font = FontMan.getFontByUsage(fallback);
+
+ return font;
+}
+
+const Graphics::Font *Gui::getTitleFont() {
+ return getFont("Chicago-12", Graphics::FontManager::kBigGUIFont);
+}
+
+void Gui::draw() {
+ if (_scene != _engine->_world->_player->_currentScene || _sceneDirty) {
+ _scene = _engine->_world->_player->_currentScene;
+
+ // Draw desktop
+ Common::Rect r(0, 0, _screen.w - 1, _screen.h - 1);
+ Design::drawFilledRoundRect(&_screen, r, kDesktopArc, kColorBlack, _patterns, kPatternCheckers);
+ g_system->copyRectToScreen(_screen.getPixels(), _screen.pitch, 0, 0, _screen.w, _screen.h);
+
+ _sceneDirty = true;
+ _consoleDirty = true;
+ _menuDirty = true;
+ _consoleFullRedraw = true;
+
+ _scene->paint(&_screen, _scene->_designBounds->left, _scene->_designBounds->top);
+
+ _sceneArea.left = _scene->_designBounds->left + kBorderWidth - 2;
+ _sceneArea.top = _scene->_designBounds->top + kBorderWidth - 2;
+ _sceneArea.setWidth(_scene->_designBounds->width() - 2 * kBorderWidth);
+ _sceneArea.setHeight(_scene->_designBounds->height() - 2 * kBorderWidth);
+
+ _consoleTextArea.left = _scene->_textBounds->left + kBorderWidth - 2;
+ _consoleTextArea.top = _scene->_textBounds->top + kBorderWidth - 2;
+ _consoleTextArea.setWidth(_scene->_textBounds->width() - 2 * kBorderWidth);
+ _consoleTextArea.setHeight(_scene->_textBounds->height() - 2 * kBorderWidth);
+ }
+
+ if (_scene && (_bordersDirty || _sceneDirty))
+ paintBorder(&_screen, _sceneArea, kWindowScene);
+
+ // Render console
+ if (_consoleDirty || _consoleFullRedraw)
+ renderConsole(&_screen, _consoleTextArea);
+
+ if (_bordersDirty || _consoleDirty || _consoleFullRedraw)
+ paintBorder(&_screen, _consoleTextArea, kWindowConsole);
+
+ if (_menuDirty)
+ _menu->render();
+
+ if (_cursorDirty) {
+ g_system->copyRectToScreen(_screen.getBasePtr(_cursorRect.left, _cursorRect.top), _screen.pitch,
+ _cursorRect.left, _cursorRect.top, _cursorRect.width(), _cursorRect.height());
+
+ _cursorDirty = false;
+ }
+
+ _sceneDirty = false;
+ _consoleDirty = false;
+ _bordersDirty = false;
+ _menuDirty = false;
+ _consoleFullRedraw = false;
+}
+
+void Gui::drawBox(Graphics::Surface *g, int x, int y, int w, int h) {
+ Common::Rect r(x, y, x + w + 1, y + h + 1);
+
+ g->fillRect(r, kColorWhite);
+ g->frameRect(r, kColorBlack);
+}
+
+void Gui::fillRect(Graphics::Surface *g, int x, int y, int w, int h) {
+ Common::Rect r(x, y, x + w, y + h);
+
+ g->fillRect(r, kColorBlack);
+}
+
+#define ARROW_W 12
+#define ARROW_H 6
+const int arrowPixels[ARROW_H][ARROW_W] = {
+ {0,0,0,0,0,1,1,0,0,0,0,0},
+ {0,0,0,0,1,1,1,1,0,0,0,0},
+ {0,0,0,1,1,1,1,1,1,0,0,0},
+ {0,0,1,1,1,1,1,1,1,1,0,0},
+ {0,1,1,1,1,1,1,1,1,1,1,0},
+ {1,1,1,1,1,1,1,1,1,1,1,1}};
+
+void Gui::paintBorder(Graphics::Surface *g, Common::Rect &r, WindowType windowType) {
+ bool active, scrollable, closeable, closeBoxPressed, drawTitle;
+ const int size = kBorderWidth;
+ int x = r.left - size;
+ int y = r.top - size;
+ int width = r.width() + 2 * size;
+ int height = r.height() + 2 * size;
+
+ switch (windowType) {
+ case kWindowScene:
+ active = _sceneIsActive;
+ scrollable = false;
+ closeable = _sceneIsActive;
+ closeBoxPressed = false;
+ drawTitle = true;
+ break;
+ case kWindowConsole:
+ active = !_sceneIsActive;
+ scrollable = true;
+ closeable = !_sceneIsActive;
+ closeBoxPressed = false;
+ drawTitle = false;
+ break;
+ }
+
+ drawBox(g, x, y, size, size);
+ drawBox(g, x + width - size - 1, y, size, size);
+ drawBox(g, x + width - size - 1, y + height - size - 1, size, size);
+ drawBox(g, x, y + height - size - 1, size, size);
+ drawBox(g, x + size, y + 2, width - 2 * size - 1, size - 4);
+ drawBox(g, x + size, y + height - size + 1, width - 2 * size - 1, size - 4);
+ drawBox(g, x + 2, y + size, size - 4, height - 2 * size - 1);
+ drawBox(g, x + width - size + 1, y + size, size - 4, height - 2 * size - 1);
+
+ if (active) {
+ fillRect(g, x + size, y + 5, width - 2 * size - 1, 8);
+ fillRect(g, x + size, y + height - 13, width - 2 * size - 1, 8);
+ fillRect(g, x + 5, y + size, 8, height - 2 * size - 1);
+ if (!scrollable) {
+ fillRect(g, x + width - 13, y + size, 8, height - 2 * size - 1);
+ } else {
+ int x1 = x + width - 15;
+ int y1 = y + size + 1;
+ for (int yy = 0; yy < ARROW_H; yy++) {
+ for (int xx = 0; xx < ARROW_W; xx++) {
+ if (arrowPixels[yy][xx] != 0) {
+ g->hLine(x1 + xx, y1 + yy, x1 + xx, kColorBlack);
+ } else {
+ g->hLine(x1 + xx, y1 + yy, x1 + xx, kColorWhite);
+ }
+ }
+ }
+ fillRect(g, x + width - 13, y + size + ARROW_H, 8, height - 2 * size - 1 - ARROW_H * 2);
+ y1 += height - 2 * size - ARROW_H - 2;
+ for (int yy = 0; yy < ARROW_H; yy++) {
+ for (int xx = 0; xx < ARROW_W; xx++) {
+ if (arrowPixels[ARROW_H - yy - 1][xx] != 0) {
+ g->hLine(x1 + xx, y1 + yy, x1 + xx, kColorBlack);
+ } else {
+ g->hLine(x1 + xx, y1 + yy, x1 + xx, kColorWhite);
+ }
+ }
+ }
+ }
+ if (closeable) {
+ if (closeBoxPressed) {
+ fillRect(g, x + 6, y + 6, 6, 6);
+ } else {
+ drawBox(g, x + 5, y + 5, 7, 7);
+ }
+ }
+ }
+
+ if (drawTitle) {
+ const Graphics::Font *font = getTitleFont();
+ int yOff = _builtInFonts ? 3 : 1;
+
+ int w = font->getStringWidth(_scene->_name) + 10;
+ int maxWidth = width - size * 2 - 7;
+ if (w > maxWidth)
+ w = maxWidth;
+ drawBox(g, x + (width - w) / 2, y, w, size);
+ font->drawString(g, _scene->_name, x + (width - w) / 2 + 5, y + yOff, w, kColorBlack);
+ }
+
+ if (x + width > _screen.w)
+ width = _screen.w - x;
+ if (y + height > _screen.h)
+ height = _screen.h - y;
+
+ g_system->copyRectToScreen(g->getBasePtr(x, y), g->pitch, x, y, width, height);
+}
+
+void Gui::loadFonts() {
+ Common::Archive *dat;
+
+ dat = Common::makeZipArchive("wage.dat");
+
+ if (!dat) {
+ warning("Could not find wage.dat. Falling back to built-in fonts");
+ _builtInFonts = true;
+
+ return;
+ }
+
+ Common::ArchiveMemberList list;
+ dat->listMembers(list);
+
+ for (Common::ArchiveMemberList::iterator it = list.begin(); it != list.end(); ++it) {
+ Common::SeekableReadStream *stream = dat->createReadStreamForMember((*it)->getName());
+
+ Graphics::BdfFont *font = Graphics::BdfFont::loadFont(*stream);
+
+ delete stream;
+
+ Common::String fontName = (*it)->getName();
+
+ // Trim the .bdf extension
+ for (int i = fontName.size() - 1; i >= 0; --i) {
+ if (fontName[i] == '.') {
+ while ((uint)i < fontName.size()) {
+ fontName.deleteLastChar();
+ }
+ break;
+ }
+ }
+
+ FontMan.assignFontToName(fontName, font);
+
+ debug(2, " %s", fontName.c_str());
+ }
+
+ _builtInFonts = false;
+
+ delete dat;
+}
+
+void Gui::regenCommandsMenu() {
+ _menu->regenCommandsMenu();
+}
+
+void Gui::regenWeaponsMenu() {
+ _menu->regenWeaponsMenu();
+}
+
+void Gui::processMenuShortCut(byte flags, uint16 ascii) {
+ _menu->processMenuShortCut(flags, ascii);
+}
+
+void Gui::mouseMove(int x, int y) {
+ if (_menu->_menuActivated) {
+ if (_menu->mouseMove(x, y))
+ _menuDirty = true;
+
+ return;
+ }
+
+ if (_inTextSelection) {
+ updateTextSelection(x, y);
+ return;
+ }
+
+ if (_consoleTextArea.contains(x, y)) {
+ if (_cursorIsArrow) {
+ CursorMan.replaceCursor(macCursorBeam, 11, 16, 3, 8, 3);
+ _cursorIsArrow = false;
+ }
+ } else if (_cursorIsArrow == false) {
+ CursorMan.replaceCursor(macCursorArrow, 11, 16, 1, 1, 3);
+ _cursorIsArrow = true;
+ }
+}
+
+void Gui::pushArrowCursor() {
+ CursorMan.pushCursor(macCursorArrow, 11, 16, 1, 1, 3);
+}
+
+void Gui::popCursor() {
+ CursorMan.popCursor();
+}
+
+Designed *Gui::mouseUp(int x, int y) {
+ if (_menu->_menuActivated) {
+ if (_menu->mouseRelease(x, y)) {
+ _sceneDirty = true;
+ _consoleDirty = true;
+ _bordersDirty = true;
+ _menuDirty = true;
+ }
+
+ return NULL;
+ }
+
+ if (_inTextSelection) {
+ _inTextSelection = false;
+
+ if (_selectionEndY == -1 ||
+ (_selectionEndX == _selectionStartX && _selectionEndY == _selectionStartY)) {
+ _selectionStartY = _selectionEndY = -1;
+ _consoleFullRedraw = true;
+ _menu->enableCommand(kMenuEdit, kMenuActionCopy, false);
+ } else {
+ _menu->enableCommand(kMenuEdit, kMenuActionCopy, true);
+
+ bool cutAllowed = false;
+
+ if (_selectionStartY == _selectionEndY && _selectionStartY == (int)_lines.size() - 1)
+ cutAllowed = true;
+
+ _menu->enableCommand(kMenuEdit, kMenuActionCut, cutAllowed);
+ _menu->enableCommand(kMenuEdit, kMenuActionClear, cutAllowed);
+ }
+ }
+
+ if (_sceneArea.contains(x, y)) {
+ if (!_sceneIsActive) {
+ _sceneIsActive = true;
+ _bordersDirty = true;
+ }
+
+ for (ObjList::const_iterator it = _scene->_objs.begin(); it != _scene->_objs.end(); ++it) {
+ if ((*it)->_design->isPointOpaque(x - _sceneArea.left + kBorderWidth, y - _sceneArea.top + kBorderWidth))
+ return *it;
+ }
+
+ for (ChrList::const_iterator it = _scene->_chrs.begin(); it != _scene->_chrs.end(); ++it) {
+ if ((*it)->_design->isPointOpaque(x - _sceneArea.left + kBorderWidth, y - _sceneArea.top + kBorderWidth))
+ return *it;
+ }
+ } else if (_consoleTextArea.contains(x, y)) {
+ if (_sceneIsActive) {
+ _sceneIsActive = false;
+ _bordersDirty = true;
+ }
+ }
+
+ return NULL;
+}
+
+void Gui::mouseDown(int x, int y) {
+ if (_menu->mouseClick(x, y)) {
+ _menuDirty = true;
+ } else if (_consoleTextArea.contains(x, y)) {
+ startMarking(x, y);
+ }
+}
+
+int Gui::calcTextX(int x, int textLine) {
+ const Graphics::Font *font = getConsoleFont();
+
+ if ((uint)textLine >= _lines.size())
+ return 0;
+
+ Common::String str = _lines[textLine];
+
+ x -= _consoleTextArea.left;
+
+ for (int i = str.size(); i >= 0; i--) {
+ if (font->getStringWidth(str) < x) {
+ return i;
+ }
+
+ str.deleteLastChar();
+ }
+
+ return 0;
+}
+
+int Gui::calcTextY(int y) {
+ y -= _consoleTextArea.top;
+
+ if (y < 0)
+ y = 0;
+
+ const int firstLine = _scrollPos / _consoleLineHeight;
+ int textLine = (y - _scrollPos % _consoleLineHeight) / _consoleLineHeight + firstLine;
+
+ return textLine;
+}
+
+void Gui::startMarking(int x, int y) {
+ _selectionStartY = calcTextY(y);
+ _selectionStartX = calcTextX(x, _selectionStartY);
+
+ _selectionEndY = -1;
+
+ _inTextSelection = true;
+}
+
+void Gui::updateTextSelection(int x, int y) {
+ _selectionEndY = calcTextY(y);
+ _selectionEndX = calcTextX(x, _selectionEndY);
+
+ _consoleFullRedraw = true;
+}
+
+} // End of namespace Wage
diff --git a/engines/wage/gui.h b/engines/wage/gui.h
new file mode 100644
index 0000000000..8cdc827bf0
--- /dev/null
+++ b/engines/wage/gui.h
@@ -0,0 +1,178 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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/fontman.h"
+#include "graphics/surface.h"
+#include "common/rect.h"
+
+namespace Wage {
+
+class Menu;
+
+enum WindowType {
+ kWindowScene,
+ kWindowConsole
+};
+
+enum {
+ kMenuHeight = 20,
+ kMenuLeftMargin = 7,
+ kMenuSpacing = 13,
+ kMenuPadding = 16,
+ kMenuDropdownPadding = 14,
+ kMenuDropdownItemHeight = 16,
+ kMenuItemHeight = 20,
+ kBorderWidth = 17,
+ kDesktopArc = 7,
+ kComponentsPadding = 10,
+ kCursorHeight = 12
+};
+
+enum {
+ kPatternSolid = 1,
+ kPatternStripes = 2,
+ kPatternCheckers = 3,
+ kPatternCheckers2 = 4
+};
+
+class Gui {
+public:
+ Gui(WageEngine *engine);
+ ~Gui();
+
+ void draw();
+ void appendText(const char *str);
+ void clearOutput();
+ void mouseMove(int x, int y);
+ void mouseDown(int x, int y);
+ Designed *mouseUp(int x, int y);
+ void drawInput();
+ void setSceneDirty() { _sceneDirty = true; }
+ const Graphics::Font *getFont(const char *name, Graphics::FontManager::FontUsage fallback);
+ void regenCommandsMenu();
+ void regenWeaponsMenu();
+ void processMenuShortCut(byte flags, uint16 ascii);
+ void pushArrowCursor();
+ void popCursor();
+
+ void actionCopy();
+ void actionPaste();
+ void actionUndo();
+ void actionClear();
+ void actionCut();
+ void disableUndo();
+
+private:
+ void undrawCursor();
+ void paintBorder(Graphics::Surface *g, Common::Rect &r, WindowType windowType);
+ void renderConsole(Graphics::Surface *g, Common::Rect &r);
+ void drawBox(Graphics::Surface *g, int x, int y, int w, int h);
+ void fillRect(Graphics::Surface *g, int x, int y, int w, int h);
+ void loadFonts();
+ void flowText(Common::String &str);
+ const Graphics::Font *getConsoleFont();
+ const Graphics::Font *getTitleFont();
+ void startMarking(int x, int y);
+ int calcTextX(int x, int textLine);
+ int calcTextY(int y);
+ void updateTextSelection(int x, int y);
+
+public:
+ Graphics::Surface _screen;
+ int _cursorX, _cursorY;
+ bool _cursorState;
+ Common::Rect _consoleTextArea;
+ bool _cursorOff;
+
+ bool _builtInFonts;
+ WageEngine *_engine;
+
+ Patterns _patterns;
+
+ bool _cursorDirty;
+ Common::Rect _cursorRect;
+
+private:
+ Graphics::Surface _console;
+ Menu *_menu;
+ Scene *_scene;
+ bool _sceneDirty;
+ bool _consoleDirty;
+ bool _bordersDirty;
+ bool _menuDirty;
+
+ Common::StringArray _out;
+ Common::StringArray _lines;
+ uint _scrollPos;
+ int _consoleLineHeight;
+ uint _consoleNumLines;
+ bool _consoleFullRedraw;
+
+ Common::Rect _sceneArea;
+ bool _sceneIsActive;
+ bool _cursorIsArrow;
+
+ bool _inTextSelection;
+ int _selectionStartX;
+ int _selectionStartY;
+ int _selectionEndX;
+ int _selectionEndY;
+
+ Common::String _clipboard;
+ Common::String _undobuffer;
+
+ int _inputTextLineNum;
+};
+
+} // End of namespace Wage
+
+#endif
diff --git a/engines/wage/menu.cpp b/engines/wage/menu.cpp
new file mode 100644
index 0000000000..b859867e9a
--- /dev/null
+++ b/engines/wage/menu.cpp
@@ -0,0 +1,563 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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/system.h"
+#include "common/keyboard.h"
+
+#include "wage/wage.h"
+#include "wage/entities.h"
+#include "wage/design.h"
+#include "wage/gui.h"
+#include "wage/menu.h"
+#include "wage/world.h"
+
+namespace Wage {
+
+struct MenuSubItem {
+ Common::String text;
+ int action;
+ int style;
+ char shortcut;
+ bool enabled;
+ Common::Rect bbox;
+
+ MenuSubItem(const char *t, int a, int s = 0, char sh = 0, bool e = true) : text(t), action(a), style(s), shortcut(sh), enabled(e) {}
+};
+
+typedef Common::Array<MenuSubItem *> SubItemArray;
+
+struct MenuItem {
+ Common::String name;
+ SubItemArray subitems;
+ Common::Rect bbox;
+ Common::Rect subbbox;
+
+ MenuItem(const char *n) : name(n) {}
+};
+
+struct MenuData {
+ int menunum;
+ const char *title;
+ int action;
+ byte shortcut;
+ bool enabled;
+} static const menuSubItems[] = {
+ { kMenuFile, "New", kMenuActionNew, 0, false },
+ { kMenuFile, "Open...", kMenuActionOpen, 0, false },
+ { kMenuFile, "Close", kMenuActionClose, 0, true },
+ { 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 }
+};
+
+Menu::Menu(Gui *gui) : _gui(gui) {
+ assert(_gui->_engine);
+ assert(_gui->_engine->_world);
+
+ _font = getMenuFont();
+
+ MenuItem *about = new MenuItem(_gui->_builtInFonts ? "\xa9" : "\xf0"); // (c) Symbol as the most resembling apple
+ _items.push_back(about);
+ _items[0]->subitems.push_back(new MenuSubItem(_gui->_engine->_world->getAboutMenuItemName(), kMenuActionAbout));
+
+ MenuItem *file = new MenuItem("File");
+ _items.push_back(file);
+
+ MenuItem *edit = new MenuItem("Edit");
+ _items.push_back(edit);
+
+ for (int i = 0; menuSubItems[i].menunum; i++) {
+ const MenuData *m = &menuSubItems[i];
+
+ _items[m->menunum]->subitems.push_back(new MenuSubItem(m->title, m->action, 0, m->shortcut, m->enabled));
+ }
+
+ _commands = new MenuItem(_gui->_engine->_world->_commandsMenuName.c_str());
+ _items.push_back(_commands);
+ regenCommandsMenu();
+
+ if (!_gui->_engine->_world->_weaponMenuDisabled) {
+ _weapons = new MenuItem(_gui->_engine->_world->_weaponsMenuName.c_str());
+ _items.push_back(_weapons);
+
+ regenWeaponsMenu();
+ }
+
+ // Calculate menu dimensions
+ int y = 1;
+ int x = 18;
+
+ for (uint i = 0; i < _items.size(); i++) {
+ int w = _font->getStringWidth(_items[i]->name);
+
+ if (_items[i]->bbox.bottom == 0) {
+ _items[i]->bbox.left = x - kMenuLeftMargin;
+ _items[i]->bbox.top = y;
+ _items[i]->bbox.right = x + w + kMenuSpacing - kMenuLeftMargin;
+ _items[i]->bbox.bottom = y + _font->getFontHeight() + (_gui->_builtInFonts ? 3 : 2);
+ }
+
+ calcMenuBounds(_items[i]);
+
+ x += w + kMenuSpacing;
+ }
+
+ _bbox.left = 0;
+ _bbox.top = 0;
+ _bbox.right = _gui->_screen.w - 1;
+ _bbox.bottom = kMenuHeight - 1;
+
+ _menuActivated = false;
+ _activeItem = -1;
+ _activeSubItem = -1;
+
+ _screenCopy.create(_gui->_screen.w, _gui->_screen.h, Graphics::PixelFormat::createFormatCLUT8());
+ _tempSurface.create(_gui->_screen.w, _font->getFontHeight(), Graphics::PixelFormat::createFormatCLUT8());
+}
+
+Menu::~Menu() {
+ for (uint i = 0; i < _items.size(); i++) {
+ for (uint j = 0; j < _items[i]->subitems.size(); j++)
+ delete _items[i]->subitems[j];
+ delete _items[i];
+ }
+}
+
+void Menu::regenCommandsMenu() {
+ for (uint j = 0; j < _commands->subitems.size(); j++)
+ delete _commands->subitems[j];
+
+ _commands->subitems.clear();
+
+ createCommandsMenu(_commands);
+ calcMenuBounds(_commands);
+}
+
+void Menu::createCommandsMenu(MenuItem *menu) {
+ Common::String string(_gui->_engine->_world->_commandsMenu);
+
+ Common::String item;
+
+ for (uint i = 0; i < string.size(); i++) {
+ while(i < string.size() && string[i] != ';') // Read token
+ item += string[i++];
+
+ if (item == "(-") {
+ menu->subitems.push_back(new MenuSubItem(NULL, 0));
+ } else {
+ bool enabled = true;
+ int style = 0;
+ char shortcut = 0;
+ const char *shortPtr = strrchr(item.c_str(), '/');
+ if (shortPtr != NULL) {
+ if (strlen(shortPtr) == 2) {
+ shortcut = shortPtr[1];
+ item.deleteLastChar();
+ item.deleteLastChar();
+ } else {
+ error("Unexpected shortcut: '%s', item '%s' in menu '%s'", shortPtr, item.c_str(), string.c_str());
+ }
+ }
+
+ while (item.size() >= 2 && item[item.size() - 2] == '<') {
+ char c = item.lastChar();
+ if (c == 'B') {
+ style |= kFontStyleBold;
+ } else if (c == 'I') {
+ style |= kFontStyleItalic;
+ } else if (c == 'U') {
+ style |= kFontStyleUnderline;
+ } else if (c == 'O') {
+ style |= kFontStyleOutline;
+ } else if (c == 'S') {
+ style |= kFontStyleShadow;
+ } else if (c == 'C') {
+ style |= kFontStyleCondensed;
+ } else if (c == 'E') {
+ style |= kFontStyleExtended;
+ }
+ item.deleteLastChar();
+ item.deleteLastChar();
+ }
+
+ Common::String tmpitem(item);
+ tmpitem.trim();
+ if (tmpitem[0] == '(') {
+ enabled = false;
+
+ for (uint j = 0; j < item.size(); j++)
+ if (item[j] == '(') {
+ item.deleteChar(j);
+ break;
+ }
+ }
+
+ menu->subitems.push_back(new MenuSubItem(item.c_str(), kMenuActionCommand, style, shortcut, enabled));
+ }
+
+ item.clear();
+ }
+}
+
+void Menu::regenWeaponsMenu() {
+ if (_gui->_engine->_world->_weaponMenuDisabled)
+ return;
+
+ for (uint j = 0; j < _weapons->subitems.size(); j++)
+ delete _weapons->subitems[j];
+
+ _weapons->subitems.clear();
+
+ createWeaponsMenu(_weapons);
+ calcMenuBounds(_weapons);
+}
+
+void Menu::createWeaponsMenu(MenuItem *menu) {
+ Chr *player = _gui->_engine->_world->_player;
+ ObjArray *weapons = player->getWeapons(true);
+
+ 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->subitems.push_back(new MenuSubItem(command.c_str(), kMenuActionCommand, 0, 0, true));
+ }
+ }
+ delete weapons;
+
+ if (menu->subitems.empty())
+ menu->subitems.push_back(new MenuSubItem("You have no weapons", 0, 0, 0, false));
+}
+
+const Graphics::Font *Menu::getMenuFont() {
+ return _gui->getFont("Chicago-12", Graphics::FontManager::kBigGUIFont);
+}
+
+const char *Menu::getAcceleratorString(MenuSubItem *item, const char *prefix) {
+ static char res[20];
+ *res = 0;
+
+ if (item->shortcut != 0)
+ sprintf(res, "%s%c%c", prefix, (_gui->_builtInFonts ? '^' : '\x11'), item->shortcut);
+
+ return res;
+}
+
+int Menu::calculateMenuWidth(MenuItem *menu) {
+ int maxWidth = 0;
+ for (uint i = 0; i < menu->subitems.size(); i++) {
+ MenuSubItem *item = menu->subitems[i];
+ if (!item->text.empty()) {
+ Common::String text(item->text);
+ Common::String acceleratorText(getAcceleratorString(item, " "));
+ if (!acceleratorText.empty()) {
+ text += acceleratorText;
+ }
+
+ int width = _font->getStringWidth(text);
+ if (width > maxWidth) {
+ maxWidth = width;
+ }
+ }
+ }
+ return maxWidth;
+}
+
+void Menu::calcMenuBounds(MenuItem *menu) {
+ // TODO: cache maxWidth
+ int maxWidth = calculateMenuWidth(menu);
+ int x1 = menu->bbox.left - 1;
+ int y1 = menu->bbox.bottom + 1;
+ int x2 = x1 + maxWidth + kMenuDropdownPadding * 2 - 4;
+ int y2 = y1 + menu->subitems.size() * kMenuDropdownItemHeight + 2;
+
+ menu->subbbox.left = x1;
+ menu->subbbox.top = y1;
+ menu->subbbox.right = x2;
+ menu->subbbox.bottom = y2;
+}
+
+void Menu::render() {
+ Common::Rect r(_bbox);
+
+ Design::drawFilledRoundRect(&_gui->_screen, r, kDesktopArc, kColorWhite, _gui->_patterns, kPatternSolid);
+ r.top = 7;
+ Design::drawFilledRect(&_gui->_screen, r, kColorWhite, _gui->_patterns, kPatternSolid);
+ r.top = kMenuHeight - 1;
+ Design::drawFilledRect(&_gui->_screen, r, kColorBlack, _gui->_patterns, kPatternSolid);
+
+ for (uint i = 0; i < _items.size(); i++) {
+ int color = kColorBlack;
+ MenuItem *it = _items[i];
+
+ if ((uint)_activeItem == i) {
+ Common::Rect hbox = it->bbox;
+
+ hbox.left -= 1;
+ hbox.right += 2;
+
+ Design::drawFilledRect(&_gui->_screen, hbox, kColorBlack, _gui->_patterns, kPatternSolid);
+ color = kColorWhite;
+
+ if (!it->subitems.empty())
+ renderSubmenu(it);
+ }
+
+ _font->drawString(&_gui->_screen, it->name, it->bbox.left + kMenuLeftMargin, it->bbox.top + (_gui->_builtInFonts ? 2 : 1), it->bbox.width(), color);
+ }
+
+ g_system->copyRectToScreen(_gui->_screen.getPixels(), _gui->_screen.pitch, 0, 0, _gui->_screen.w, kMenuHeight);
+}
+
+void Menu::renderSubmenu(MenuItem *menu) {
+ Common::Rect *r = &menu->subbbox;
+
+ if (r->width() == 0 || r->height() == 0)
+ return;
+
+ Design::drawFilledRect(&_gui->_screen, *r, kColorWhite, _gui->_patterns, kPatternSolid);
+ Design::drawRect(&_gui->_screen, *r, 1, kColorBlack, _gui->_patterns, kPatternSolid);
+ Design::drawVLine(&_gui->_screen, r->right + 1, r->top + 3, r->bottom + 1, 1, kColorBlack, _gui->_patterns, kPatternSolid);
+ Design::drawHLine(&_gui->_screen, r->left + 3, r->right + 1, r->bottom + 1, 1, kColorBlack, _gui->_patterns, kPatternSolid);
+
+ int x = r->left + kMenuDropdownPadding;
+ int y = r->top + 1;
+ for (uint i = 0; i < menu->subitems.size(); i++) {
+ Common::String text(menu->subitems[i]->text);
+ Common::String acceleratorText(getAcceleratorString(menu->subitems[i], ""));
+ int accelX = r->right - 25;
+
+ int color = kColorBlack;
+ if (i == (uint)_activeSubItem && !text.empty() && menu->subitems[i]->enabled) {
+ color = kColorWhite;
+ Common::Rect trect(r->left, y - (_gui->_builtInFonts ? 1 : 0), r->right, y + _font->getFontHeight());
+
+ Design::drawFilledRect(&_gui->_screen, trect, kColorBlack, _gui->_patterns, kPatternSolid);
+ }
+
+ if (!text.empty()) {
+ Graphics::Surface *s = &_gui->_screen;
+ int tx = x, ty = y;
+
+ if (!menu->subitems[i]->enabled) {
+ s = &_tempSurface;
+ tx = 0;
+ ty = 0;
+ accelX -= x;
+
+ _tempSurface.fillRect(Common::Rect(0, 0, _tempSurface.w, _tempSurface.h), kColorGreen);
+ }
+
+ _font->drawString(s, text, tx, ty, r->width(), color);
+
+ if (!acceleratorText.empty())
+ _font->drawString(s, acceleratorText, accelX, ty, r->width(), color);
+
+ if (!menu->subitems[i]->enabled) {
+ // I am lazy to extend drawString() with plotProc as a parameter, so
+ // fake it here
+ for (int ii = 0; ii < _tempSurface.h; ii++) {
+ const byte *src = (const byte *)_tempSurface.getBasePtr(0, ii);
+ byte *dst = (byte *)_gui->_screen.getBasePtr(x, y+ii);
+ byte pat = _gui->_patterns[kPatternCheckers2 - 1][ii % 8];
+ for (int j = 0; j < r->width(); j++) {
+ if (*src != kColorGreen && (pat & (1 << (7 - (x + j) % 8))))
+ *dst = *src;
+ src++;
+ dst++;
+ }
+ }
+ }
+ } else { // Delimiter
+ Design::drawHLine(&_gui->_screen, r->left + 1, r->right - 1, y + kMenuDropdownItemHeight / 2, 1, kColorBlack, _gui->_patterns, kPatternStripes);
+ }
+
+ y += kMenuDropdownItemHeight;
+ }
+
+ g_system->copyRectToScreen(_gui->_screen.getBasePtr(r->left, r->top), _gui->_screen.pitch, r->left, r->top, r->width() + 3, r->height() + 3);
+}
+
+bool Menu::mouseClick(int x, int y) {
+ if (_bbox.contains(x, y)) {
+ if (!_menuActivated)
+ _screenCopy.copyFrom(_gui->_screen);
+
+ for (uint i = 0; i < _items.size(); i++)
+ if (_items[i]->bbox.contains(x, y)) {
+ if ((uint)_activeItem == i)
+ return false;
+
+ if (_activeItem != -1) { // Restore background
+ Common::Rect r(_items[_activeItem]->subbbox);
+ r.right += 3;
+ r.bottom += 3;
+
+ _gui->_screen.copyRectToSurface(_screenCopy, r.left, r.top, r);
+ g_system->copyRectToScreen(_gui->_screen.getBasePtr(r.left, r.top), _gui->_screen.pitch, r.left, r.top, r.width() + 1, r.height() + 1);
+ }
+
+ _activeItem = i;
+ _activeSubItem = -1;
+ _menuActivated = true;
+
+ return true;
+ }
+ } else if (_menuActivated && _items[_activeItem]->subbbox.contains(x, y)) {
+ MenuItem *it = _items[_activeItem];
+ int numSubItem = (y - it->subbbox.top) / kMenuDropdownItemHeight;
+
+ if (numSubItem != _activeSubItem) {
+ _activeSubItem = numSubItem;
+
+ renderSubmenu(_items[_activeItem]);
+ }
+ } else if (_menuActivated && _activeItem != -1) {
+ _activeSubItem = -1;
+
+ renderSubmenu(_items[_activeItem]);
+ }
+
+ return false;
+}
+
+bool Menu::mouseMove(int x, int y) {
+ if (_menuActivated)
+ if (mouseClick(x, y))
+ return true;
+
+ return false;
+}
+
+bool Menu::mouseRelease(int x, int y) {
+ if (_menuActivated) {
+ _menuActivated = false;
+
+ if (_activeItem != -1 && _activeSubItem != -1 && _items[_activeItem]->subitems[_activeSubItem]->enabled)
+ executeCommand(_items[_activeItem]->subitems[_activeSubItem]);
+
+ _activeItem = -1;
+ _activeSubItem = -1;
+
+ return true;
+ }
+
+ return false;
+}
+
+void Menu::executeCommand(MenuSubItem *subitem) {
+ switch(subitem->action) {
+ case kMenuActionAbout:
+ case kMenuActionNew:
+ case kMenuActionOpen:
+ case kMenuActionClose:
+ case kMenuActionSave:
+ case kMenuActionSaveAs:
+ case kMenuActionRevert:
+ case kMenuActionQuit:
+
+ case kMenuActionUndo:
+ _gui->actionUndo();
+ break;
+ case kMenuActionCut:
+ _gui->actionCut();
+ break;
+ case kMenuActionCopy:
+ _gui->actionCopy();
+ break;
+ case kMenuActionPaste:
+ _gui->actionPaste();
+ break;
+ case kMenuActionClear:
+ _gui->actionClear();
+ break;
+
+ case kMenuActionCommand:
+ _gui->_engine->processTurn(&subitem->text, NULL);
+ break;
+
+ default:
+ warning("Unknown action: %d", subitem->action);
+
+ }
+}
+
+void Menu::processMenuShortCut(byte flags, uint16 ascii) {
+ ascii = tolower(ascii);
+
+ if (flags & (Common::KBD_CTRL | Common::KBD_META)) {
+ for (uint i = 0; i < _items.size(); i++)
+ for (uint j = 0; j < _items[i]->subitems.size(); j++)
+ if (_items[i]->subitems[j]->enabled && tolower(_items[i]->subitems[j]->shortcut) == ascii) {
+ executeCommand(_items[i]->subitems[j]);
+ break;
+ }
+ }
+}
+
+void Menu::enableCommand(int menunum, int action, bool state) {
+ for (uint i = 0; i < _items[menunum]->subitems.size(); i++)
+ if (_items[menunum]->subitems[i]->action == action)
+ _items[menunum]->subitems[i]->enabled = state;
+}
+
+} // End of namespace Wage
diff --git a/engines/wage/menu.h b/engines/wage/menu.h
new file mode 100644
index 0000000000..cee7611664
--- /dev/null
+++ b/engines/wage/menu.h
@@ -0,0 +1,138 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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_MENU_H
+#define WAGE_MENU_H
+
+namespace Wage {
+
+struct MenuItem;
+struct MenuSubItem;
+
+enum {
+ kFontStyleBold = 1,
+ kFontStyleItalic = 2,
+ kFontStyleUnderline = 4,
+ kFontStyleOutline = 8,
+ kFontStyleShadow = 16,
+ kFontStyleCondensed = 32,
+ kFontStyleExtended = 64
+};
+
+enum {
+ 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 Menu {
+public:
+ Menu(Gui *gui);
+ ~Menu();
+
+ void render();
+ bool mouseClick(int x, int y);
+ bool mouseRelease(int x, int y);
+ bool mouseMove(int x, int y);
+
+ void regenCommandsMenu();
+ void regenWeaponsMenu();
+ void processMenuShortCut(byte flags, uint16 ascii);
+ void enableCommand(int menunum, int action, bool state);
+
+ bool _menuActivated;
+ Common::Rect _bbox;
+
+private:
+ Gui *_gui;
+ Graphics::Surface _screenCopy;
+ Graphics::Surface _tempSurface;
+
+private:
+ const Graphics::Font *getMenuFont();
+ const char *getAcceleratorString(MenuSubItem *item, const char *prefix);
+ int calculateMenuWidth(MenuItem *menu);
+ void calcMenuBounds(MenuItem *menu);
+ void renderSubmenu(MenuItem *menu);
+ void createCommandsMenu(MenuItem *menu);
+ void createWeaponsMenu(MenuItem *menu);
+ void executeCommand(MenuSubItem *subitem);
+
+ Common::Array<MenuItem *> _items;
+ MenuItem *_weapons;
+ MenuItem *_commands;
+
+ const Graphics::Font *_font;
+
+ int _activeItem;
+ int _activeSubItem;
+};
+
+} // End of namespace Wage
+
+#endif
diff --git a/engines/wage/module.mk b/engines/wage/module.mk
new file mode 100644
index 0000000000..f91e84128d
--- /dev/null
+++ b/engines/wage/module.mk
@@ -0,0 +1,27 @@
+MODULE := engines/wage
+
+MODULE_OBJS := \
+ combat.o \
+ design.o \
+ detection.o \
+ dialog.o \
+ entities.o \
+ gui.o \
+ gui-console.o \
+ menu.o \
+ randomhat.o \
+ script.o \
+ util.o \
+ wage.o \
+ world.o
+
+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
diff --git a/engines/wage/randomhat.cpp b/engines/wage/randomhat.cpp
new file mode 100644
index 0000000000..9371140398
--- /dev/null
+++ b/engines/wage/randomhat.cpp
@@ -0,0 +1,83 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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.getVal(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
diff --git a/engines/wage/randomhat.h b/engines/wage/randomhat.h
new file mode 100644
index 0000000000..254cd2ae8d
--- /dev/null
+++ b/engines/wage/randomhat.h
@@ -0,0 +1,77 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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
diff --git a/engines/wage/script.cpp b/engines/wage/script.cpp
new file mode 100644
index 0000000000..523f1e1a1b
--- /dev/null
+++ b/engines/wage/script.cpp
@@ -0,0 +1,1146 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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/script.h"
+#include "wage/world.h"
+
+#include "common/stream.h"
+
+namespace Wage {
+
+Common::String Script::Operand::toString() {
+ switch(_type) {
+ case NUMBER:
+ return Common::String::format("%d", _value.number);
+ case STRING:
+ case TEXT_INPUT:
+ return *_value.string;
+ case OBJ:
+ return _value.obj->toString();
+ case CHR:
+ return _value.chr->toString();
+ case SCENE:
+ return _value.scene->toString();
+ case CLICK_INPUT:
+ return _value.inputClick->toString();
+ default:
+ error("Unhandled operand type: _type");
+ }
+}
+
+Script::Script(Common::SeekableReadStream *data) : _data(data) {
+ convertToText();
+}
+
+Script::~Script() {
+ for (uint i = 0; i < _scriptText.size(); i++) {
+ delete _scriptText[i];
+ }
+
+ delete _data;
+}
+
+void Script::print() {
+ for (uint i = 0; i < _scriptText.size(); i++) {
+ debug(4, "%d [%04x]: %s", i, _scriptText[i]->offset, _scriptText[i]->line.c_str());
+ }
+}
+
+void Script::printLine(int offset) {
+ for (uint i = 0; i < _scriptText.size(); i++)
+ if (_scriptText[i]->offset >= offset) {
+ debug(4, "%d [%04x]: %s", i, _scriptText[i]->offset, _scriptText[i]->line.c_str());
+ break;
+ }
+}
+
+bool Script::execute(World *world, int loopCount, Common::String *inputText, Designed *inputClick, WageEngine *engine) {
+ _world = world;
+ _loopCount = loopCount;
+ _inputText = inputText;
+ _inputClick = inputClick;
+ _engine = engine;
+ _handled = false;
+ Common::String input;
+
+ if (inputText)
+ input = *inputText;
+
+ _data->seek(12);
+ while (_data->pos() < _data->size()) {
+ printLine(_data->pos());
+
+ byte command = _data->readByte();
+
+ switch(command) {
+ case 0x80: // IF
+ processIf();
+ break;
+ case 0x87: // EXIT
+ debug(6, "exit at offset %d", _data->pos() - 1);
+
+ return true;
+ case 0x89: // MOVE
+ {
+ Scene *currentScene = _world->_player->_currentScene;
+ processMove();
+ if (_world->_player->_currentScene != currentScene)
+ return true;
+ break;
+ }
+ case 0x8B: // PRINT
+ {
+ Operand *op = readOperand();
+ // TODO check op type is string or number, or something good...
+ _handled = true;
+ _engine->appendText(op->toString().c_str());
+ delete op;
+ byte d = _data->readByte();
+ if (d != 0xFD)
+ warning("Operand 0x8B (PRINT) End Byte != 0xFD");
+ break;
+ }
+ case 0x8C: // SOUND
+ {
+ Operand *op = readOperand();
+ // TODO check op type is string.
+ _handled = true;
+ _engine->playSound(op->toString());
+ delete op;
+ byte d = _data->readByte();
+ if (d != 0xFD)
+ warning("Operand 0x8B (PRINT) End Byte != 0xFD");
+ break;
+ }
+ case 0x8E: // LET
+ processLet();
+ break;
+ case 0x95: // MENU
+ {
+ Operand *op = readStringOperand(); // allows empty menu
+ // TODO check op type is string.
+ _engine->setMenu(op->toString());
+ byte d = _data->readByte();
+ if (d != 0xFD)
+ warning("Operand 0x8B (PRINT) End Byte != 0xFD");
+ }
+ case 0x88: // END
+ break;
+ default:
+ debug(0, "Unknown opcode: %d", _data->pos());
+ }
+ }
+
+ if (_world->_globalScript != this) {
+ debug(1, "Executing global script...");
+ bool globalHandled = _world->_globalScript->execute(_world, _loopCount, &input, _inputClick, _engine);
+ if (globalHandled)
+ _handled = true;
+ } else if (!input.empty()) {
+ if (input.contains("north")) {
+ _handled = _engine->handleMoveCommand(NORTH, "north");
+ } else if (input.contains("east")) {
+ _handled = _engine->handleMoveCommand(EAST, "east");
+ } else if (input.contains("south")) {
+ _handled = _engine->handleMoveCommand(SOUTH, "south");
+ } else if (input.contains("west")) {
+ _handled = _engine->handleMoveCommand(WEST, "west");
+ } else if (input.hasPrefix("take ")) {
+ _handled = _engine->handleTakeCommand(&input.c_str()[5]);
+ } else if (input.hasPrefix("get ")) {
+ _handled = _engine->handleTakeCommand(&input.c_str()[4]);
+ } else if (input.hasPrefix("pick up ")) {
+ _handled = _engine->handleTakeCommand(&input.c_str()[8]);
+ } else if (input.hasPrefix("drop ")) {
+ _handled = _engine->handleDropCommand(&input.c_str()[5]);
+ } else if (input.hasPrefix("aim ")) {
+ _handled = _engine->handleAimCommand(&input.c_str()[4]);
+ } else if (input.hasPrefix("wear ")) {
+ _handled = _engine->handleWearCommand(&input.c_str()[5]);
+ } else if (input.hasPrefix("put on ")) {
+ _handled = _engine->handleWearCommand(&input.c_str()[7]);
+ } else if (input.hasPrefix("offer ")) {
+ _handled = _engine->handleOfferCommand(&input.c_str()[6]);
+ } else if (input.contains("look")) {
+ _handled = _engine->handleLookCommand();
+ } else if (input.contains("inventory")) {
+ _handled = _engine->handleInventoryCommand();
+ } else if (input.contains("status")) {
+ _handled = _engine->handleStatusCommand();
+ } else if (input.contains("rest") || input.equals("wait")) {
+ _handled = _engine->handleRestCommand();
+ } else if (_engine->getOffer() != NULL && input.contains("accept")) {
+ _handled = _engine->handleAcceptCommand();
+ } else {
+ Chr *player = _world->_player;
+ ObjArray *weapons = player->getWeapons(true);
+ for (ObjArray::const_iterator weapon = weapons->begin(); weapon != weapons->end(); ++weapon) {
+ if (_engine->tryAttack(*weapon, input)) {
+ _handled = _engine->handleAttack(*weapon);
+ break;
+ }
+ }
+
+ delete weapons;
+ }
+ // TODO: weapons, offer, etc...
+ } else if (_inputClick->_classType == OBJ) {
+ Obj *obj = (Obj *)_inputClick;
+ if (obj->_type != Obj::IMMOBILE_OBJECT) {
+ _engine->takeObj(obj);
+ } else {
+ _engine->appendText(obj->_clickMessage.c_str());
+ }
+
+ _handled = true;
+ }
+
+ return _handled;
+}
+
+Script::Operand *Script::readOperand() {
+ byte operandType = _data->readByte();
+
+ debug(7, "%x: readOperand: 0x%x", _data->pos(), operandType);
+
+ Context *cont = &_world->_player->_context;
+ switch (operandType) {
+ case 0xA0: // TEXT$
+ return new Operand(_inputText, TEXT_INPUT);
+ case 0xA1:
+ return new Operand(_inputClick, CLICK_INPUT);
+ case 0xC0: // STORAGE@
+ return new Operand(_world->_storageScene, SCENE);
+ case 0xC1: // SCENE@
+ return new Operand(_world->_player->_currentScene, SCENE);
+ case 0xC2: // PLAYER@
+ return new Operand(_world->_player, CHR);
+ case 0xC3: // MONSTER@
+ return new Operand(_engine->getMonster(), CHR);
+ case 0xC4: // RANDOMSCN@
+ return new Operand(_world->_orderedScenes[_engine->_rnd->getRandomNumber(_world->_orderedScenes.size())], SCENE);
+ case 0xC5: // RANDOMCHR@
+ return new Operand(_world->_orderedChrs[_engine->_rnd->getRandomNumber(_world->_orderedChrs.size())], CHR);
+ case 0xC6: // RANDOMOBJ@
+ return new Operand(_world->_orderedObjs[_engine->_rnd->getRandomNumber(_world->_orderedObjs.size())], OBJ);
+ case 0xB0: // VISITS#
+ return new Operand(cont->_visits, NUMBER);
+ case 0xB1: // RANDOM# for Star Trek, but VISITS# for some other games?
+ return new Operand(1 + _engine->_rnd->getRandomNumber(100), NUMBER);
+ case 0xB5: // RANDOM# // A random number between 1 and 100.
+ return new Operand(1 + _engine->_rnd->getRandomNumber(100), NUMBER);
+ case 0xB2: // LOOP#
+ return new Operand(_loopCount, NUMBER);
+ case 0xB3: // VICTORY#
+ return new Operand(cont->_kills, NUMBER);
+ case 0xB4: // BADCOPY#
+ return new Operand(0, NUMBER); // \?\?\??
+ case 0xFF:
+ {
+ // user variable
+ int value = _data->readByte();
+
+ // TODO: Verify that we're using the right index.
+ return new Operand(cont->_userVariables[value - 1], NUMBER);
+ }
+ case 0xD0:
+ return new Operand(cont->_statVariables[PHYS_STR_BAS], NUMBER);
+ case 0xD1:
+ return new Operand(cont->_statVariables[PHYS_HIT_BAS], NUMBER);
+ case 0xD2:
+ return new Operand(cont->_statVariables[PHYS_ARM_BAS], NUMBER);
+ case 0xD3:
+ return new Operand(cont->_statVariables[PHYS_ACC_BAS], NUMBER);
+ case 0xD4:
+ return new Operand(cont->_statVariables[SPIR_STR_BAS], NUMBER);
+ case 0xD5:
+ return new Operand(cont->_statVariables[SPIR_HIT_BAS], NUMBER);
+ case 0xD6:
+ return new Operand(cont->_statVariables[SPIR_ARM_BAS], NUMBER);
+ case 0xD7:
+ return new Operand(cont->_statVariables[SPIR_ACC_BAS], NUMBER);
+ case 0xD8:
+ return new Operand(cont->_statVariables[PHYS_SPE_BAS], NUMBER);
+ case 0xE0:
+ return new Operand(cont->_statVariables[PHYS_STR_CUR], NUMBER);
+ case 0xE1:
+ return new Operand(cont->_statVariables[PHYS_HIT_CUR], NUMBER);
+ case 0xE2:
+ return new Operand(cont->_statVariables[PHYS_ARM_CUR], NUMBER);
+ case 0xE3:
+ return new Operand(cont->_statVariables[PHYS_ACC_CUR], NUMBER);
+ case 0xE4:
+ return new Operand(cont->_statVariables[SPIR_STR_CUR], NUMBER);
+ case 0xE5:
+ return new Operand(cont->_statVariables[SPIR_HIT_CUR], NUMBER);
+ case 0xE6:
+ return new Operand(cont->_statVariables[SPIR_ARM_CUR], NUMBER);
+ case 0xE7:
+ return new Operand(cont->_statVariables[SPIR_ACC_CUR], NUMBER);
+ case 0xE8:
+ return new Operand(cont->_statVariables[PHYS_SPE_CUR], NUMBER);
+ default:
+ if (operandType >= 0x20 && operandType < 0x80) {
+ _data->seek(-1, SEEK_CUR);
+ return readStringOperand();
+ } else {
+ debug("Dunno what %x is (index=%d)!\n", operandType, _data->pos()-1);
+ }
+ return NULL;
+ }
+}
+
+void Script::assign(byte operandType, int uservar, uint16 value) {
+ Context *cont = &_world->_player->_context;
+
+ switch (operandType) {
+ case 0xFF:
+ cont->_userVariables[uservar - 1] = value;
+ break;
+ case 0xD0:
+ cont->_statVariables[PHYS_STR_BAS] = value;
+ break;
+ case 0xD1:
+ cont->_statVariables[PHYS_HIT_BAS] = value;
+ break;
+ case 0xD2:
+ cont->_statVariables[PHYS_ARM_BAS] = value;
+ break;
+ case 0xD3:
+ cont->_statVariables[PHYS_ACC_BAS] = value;
+ break;
+ case 0xD4:
+ cont->_statVariables[SPIR_STR_BAS] = value;
+ break;
+ case 0xD5:
+ cont->_statVariables[SPIR_HIT_BAS] = value;
+ break;
+ case 0xD6:
+ cont->_statVariables[SPIR_ARM_BAS] = value;
+ break;
+ case 0xD7:
+ cont->_statVariables[SPIR_ACC_BAS] = value;
+ break;
+ case 0xD8:
+ cont->_statVariables[PHYS_SPE_BAS] = value;
+ break;
+ case 0xE0:
+ cont->_statVariables[PHYS_STR_CUR] = value;
+ break;
+ case 0xE1:
+ cont->_statVariables[PHYS_HIT_CUR] = value;
+ break;
+ case 0xE2:
+ cont->_statVariables[PHYS_ARM_CUR] = value;
+ break;
+ case 0xE3:
+ cont->_statVariables[PHYS_ACC_CUR] = value;
+ break;
+ case 0xE4:
+ cont->_statVariables[SPIR_STR_CUR] = value;
+ break;
+ case 0xE5:
+ cont->_statVariables[SPIR_HIT_CUR] = value;
+ break;
+ case 0xE6:
+ cont->_statVariables[SPIR_ARM_CUR] = value;
+ break;
+ case 0xE7:
+ cont->_statVariables[SPIR_ACC_CUR] = value;
+ break;
+ case 0xE8:
+ cont->_statVariables[PHYS_SPE_CUR] = value;
+ break;
+ default:
+ debug("No idea what I'm supposed to assign! (%x at %d)!\n", operandType, _data->pos()-1);
+ }
+}
+
+Script::Operand *Script::readStringOperand() {
+ Common::String *str;
+ bool allDigits = true;
+
+ str = new Common::String();
+
+ while (true) {
+ byte c = _data->readByte();
+ if (c >= 0x20 && c < 0x80)
+ *str += c;
+ else
+ break;
+ if (c < '0' || c > '9')
+ allDigits = false;
+ }
+ _data->seek(-1, SEEK_CUR);
+
+ if (allDigits && !str->empty()) {
+ int r = atol(str->c_str());
+ delete str;
+
+ return new Operand(r, NUMBER);
+ } else {
+ // TODO: This string could be a room name or something like that.
+ return new Operand(str, STRING);
+ }
+}
+
+const char *Script::readOperator() {
+ byte cmd = _data->readByte();
+
+ debug(7, "readOperator: 0x%x", cmd);
+ switch (cmd) {
+ case 0x81:
+ return "=";
+ case 0x82:
+ return "<";
+ case 0x83:
+ return ">";
+ case 0x8f:
+ return "+";
+ case 0x90:
+ return "-";
+ case 0x91:
+ return "*";
+ case 0x92:
+ return "/";
+ case 0x93:
+ return "==";
+ case 0x94:
+ return ">>";
+ case 0xfd:
+ return ";";
+ default:
+ warning("UNKNOWN OP %x", cmd);
+ }
+ return NULL;
+}
+
+void Script::processIf() {
+ int logicalOp = 0; // 0 => initial, 1 => and, 2 => or
+ bool result = true;
+ bool done = false;
+
+ do {
+ Operand *lhs = readOperand();
+ const char *op = readOperator();
+ Operand *rhs = readOperand();
+
+ bool condResult = eval(lhs, op, rhs);
+
+ delete lhs;
+ delete rhs;
+
+ if (logicalOp == 1) {
+ result = (result && condResult);
+ } else if (logicalOp == 2) {
+ result = (result || condResult);
+ } else { // logicalOp == 0
+ result = condResult;
+ }
+
+ byte logical = _data->readByte();
+
+ if (logical == 0x84) {
+ logicalOp = 1; // and
+ } else if (logical == 0x85) {
+ logicalOp = 2; // or
+ } else if (logical == 0xFE) {
+ done = true; // then
+ }
+ } while (!done);
+
+ if (result == false) {
+ skipBlock();
+ }
+}
+
+void Script::skipIf() {
+ do {
+ Operand *lhs = readOperand();
+ readOperator();
+ Operand *rhs = readOperand();
+
+ delete lhs;
+ delete rhs;
+ } while (_data->readByte() != 0xFE);
+}
+
+void Script::skipBlock() {
+ int nesting = 1;
+
+ while (true) {
+ byte op = _data->readByte();
+
+ if (_data->eos())
+ return;
+
+ if (op == 0x80) { // IF
+ nesting++;
+ skipIf();
+ } else if (op == 0x88 || op == 0x87) { // END or EXIT
+ nesting--;
+ if (nesting == 0) {
+ return;
+ }
+ } else switch (op) {
+ case 0x8B: // PRINT
+ case 0x8C: // SOUND
+ case 0x8E: // LET
+ case 0x95: // MENU
+ while (_data->readByte() != 0xFD)
+ ;
+ }
+ }
+}
+
+enum {
+ kCompEqNumNum,
+ kCompEqObjScene,
+ kCompEqChrScene,
+ kCompEqObjChr,
+ kCompEqChrChr,
+ kCompEqSceneScene,
+ kCompEqStringTextInput,
+ kCompEqTextInputString,
+ kCompEqNumberTextInput,
+ kCompEqTextInputNumber,
+ kCompLtNumNum,
+ kCompLtStringTextInput,
+ kCompLtTextInputString,
+ kCompLtObjChr,
+ kCompLtChrObj,
+ kCompLtObjScene,
+ kCompGtNumNum,
+ kCompGtStringString,
+ kCompGtChrScene,
+ kMoveObjChr,
+ kMoveObjScene,
+ kMoveChrScene
+};
+
+struct Comparator {
+ char op;
+ OperandType o1;
+ OperandType o2;
+ int cmp;
+} static comparators[] = {
+ { '=', NUMBER, NUMBER, kCompEqNumNum },
+ { '=', OBJ, SCENE, kCompEqObjScene },
+ { '=', CHR, SCENE, kCompEqChrScene },
+ { '=', OBJ, CHR, kCompEqObjChr },
+ { '=', CHR, CHR, kCompEqChrChr },
+ { '=', SCENE, SCENE, kCompEqSceneScene },
+ { '=', STRING, TEXT_INPUT, kCompEqStringTextInput },
+ { '=', TEXT_INPUT, STRING, kCompEqTextInputString },
+ { '=', NUMBER, TEXT_INPUT, kCompEqNumberTextInput },
+ { '=', TEXT_INPUT, NUMBER, kCompEqTextInputNumber },
+
+ { '<', NUMBER, NUMBER, kCompLtNumNum },
+ { '<', STRING, TEXT_INPUT, kCompLtStringTextInput },
+ { '<', TEXT_INPUT, STRING, kCompLtTextInputString },
+ { '<', OBJ, CHR, kCompLtObjChr },
+ { '<', CHR, OBJ, kCompLtChrObj },
+ { '<', OBJ, SCENE, kCompLtObjScene },
+ { '<', CHR, CHR, kCompEqChrChr }, // Same logic as =
+ { '<', SCENE, SCENE, kCompEqSceneScene },
+
+ { '>', NUMBER, NUMBER, kCompGtNumNum },
+ { '>', TEXT_INPUT, STRING, kCompLtTextInputString }, // Same logic as <
+ //FIXME: this prevents the below cases from working due to exact
+ //matches taking precedence over conversions...
+ //{ '>', STRING, STRING, kCompGtStringString }, // Same logic as <
+ { '>', OBJ, CHR, kCompLtObjChr }, // Same logic as <
+ { '>', OBJ, SCENE, kCompLtObjScene }, // Same logic as <
+ { '>', CHR, SCENE, kCompGtChrScene },
+
+ { 'M', OBJ, CHR, kMoveObjChr },
+ { 'M', OBJ, SCENE, kMoveObjScene },
+ { 'M', CHR, SCENE, kMoveChrScene },
+ { 0, OBJ, OBJ, 0 }
+};
+
+bool Script::compare(Operand *o1, Operand *o2, int comparator) {
+ switch(comparator) {
+ case kCompEqNumNum:
+ return o1->_value.number == o2->_value.number;
+ case kCompEqObjScene:
+ for (ObjList::const_iterator it = o2->_value.scene->_objs.begin(); it != o2->_value.scene->_objs.end(); ++it)
+ if (*it == o1->_value.obj)
+ return true;
+ return false;
+ case kCompEqChrScene:
+ for (ChrList::const_iterator it = o2->_value.scene->_chrs.begin(); it != o2->_value.scene->_chrs.end(); ++it)
+ if (*it == o1->_value.chr)
+ return true;
+ return false;
+ case kCompEqObjChr:
+ for (ObjArray::const_iterator it = o2->_value.chr->_inventory.begin(); it != o2->_value.chr->_inventory.end(); ++it)
+ if (*it == o1->_value.obj)
+ return true;
+ return false;
+ case kCompEqChrChr:
+ return o1->_value.chr == o2->_value.chr;
+ case kCompEqSceneScene:
+ return o1->_value.scene == o2->_value.scene;
+ case kCompEqStringTextInput:
+ if (_inputText == NULL) {
+ return false;
+ } else {
+ Common::String s1(*_inputText), s2(*o1->_value.string);
+ s1.toLowercase();
+ s2.toLowercase();
+
+ return s1.contains(s2);
+ }
+ case kCompEqTextInputString:
+ return compare(o2, o1, kCompEqStringTextInput);
+ case kCompEqNumberTextInput:
+ if (_inputText == NULL) {
+ return false;
+ } else {
+ Common::String s1(*_inputText), s2(o1->toString());
+ s1.toLowercase();
+ s2.toLowercase();
+
+ return s1.contains(s2);
+ }
+ case kCompEqTextInputNumber:
+ if (_inputText == NULL) {
+ return false;
+ } else {
+ Common::String s1(*_inputText), s2(o2->toString());
+ s1.toLowercase();
+ s2.toLowercase();
+
+ return s1.contains(s2);
+ }
+ case kCompLtNumNum:
+ return o1->_value.number < o2->_value.number;
+ case kCompLtStringTextInput:
+ return !compare(o1, o2, kCompEqStringTextInput);
+ case kCompLtTextInputString:
+ return !compare(o2, o1, kCompEqStringTextInput);
+ case kCompLtObjChr:
+ return o1->_value.obj->_currentOwner != o2->_value.chr;
+ case kCompLtChrObj:
+ return compare(o2, o1, kCompLtObjChr);
+ case kCompLtObjScene:
+ return o1->_value.obj->_currentScene != o2->_value.scene;
+ case kCompGtNumNum:
+ return o1->_value.number > o2->_value.number;
+ case kCompGtStringString:
+ return o1->_value.string == o2->_value.string;
+ case kCompGtChrScene:
+ return (o1->_value.chr != NULL && o1->_value.chr->_currentScene != o2->_value.scene);
+ case kMoveObjChr:
+ if (o1->_value.obj->_currentOwner != o2->_value.chr) {
+ _world->move(o1->_value.obj, o2->_value.chr);
+ _handled = true; // TODO: Is this correct?
+ }
+ break;
+ case kMoveObjScene:
+ if (o1->_value.obj->_currentScene != o2->_value.scene) {
+ _world->move(o1->_value.obj, o2->_value.scene);
+ // Note: This shouldn't call setHandled() - see
+ // Sultan's Palace 'Food and Drink' scene.
+ }
+ break;
+ case kMoveChrScene:
+ _world->move(o1->_value.chr, o2->_value.scene);
+ _handled = true; // TODO: Is this correct?
+ break;
+ }
+
+ return false;
+}
+
+bool Script::evaluatePair(Operand *lhs, const char *op, Operand *rhs) {
+ for (int cmp = 0; comparators[cmp].op != 0; cmp++) {
+ if (comparators[cmp].op != op[0])
+ continue;
+
+ if (comparators[cmp].o1 == lhs->_type && comparators[cmp].o2 == rhs->_type)
+ return compare(lhs, rhs, comparators[cmp].cmp);
+ }
+
+ // Now, try partial matches.
+ Operand *c1, *c2;
+ for (int cmp = 0; comparators[cmp].op != 0; cmp++) {
+ if (comparators[cmp].op != op[0])
+ continue;
+
+ if (comparators[cmp].o1 == lhs->_type &&
+ (c2 = convertOperand(rhs, comparators[cmp].o2)) != NULL) {
+ bool res = compare(lhs, c2, comparators[cmp].cmp);
+ delete c2;
+ return res;
+ } else if (comparators[cmp].o2 == rhs->_type &&
+ (c1 = convertOperand(lhs, comparators[cmp].o1)) != NULL) {
+ bool res = compare(c1, rhs, comparators[cmp].cmp);
+ delete c1;
+ return res;
+ }
+ }
+
+ // Now, try double conversion.
+ for (int cmp = 0; comparators[cmp].op != 0; cmp++) {
+ if (comparators[cmp].op != op[0])
+ continue;
+
+ if (comparators[cmp].o1 == lhs->_type || comparators[cmp].o2 == rhs->_type)
+ continue;
+
+ if ((c1 = convertOperand(lhs, comparators[cmp].o1)) != NULL) {
+ if ((c2 = convertOperand(rhs, comparators[cmp].o2)) != NULL) {
+ bool res = compare(c1, c2, comparators[cmp].cmp);
+ delete c1;
+ delete c2;
+ return res;
+ }
+ delete c1;
+ }
+ }
+
+ warning("UNHANDLED CASE: [lhs=%d/%s, op=%s rhs=%d/%s]",
+ lhs->_type, lhs->toString().c_str(), op, rhs->_type, rhs->toString().c_str());
+
+
+ return false;
+}
+
+bool Script::eval(Operand *lhs, const char *op, Operand *rhs) {
+ bool result = false;
+
+ if (lhs->_type == CLICK_INPUT || rhs->_type == CLICK_INPUT) {
+ return evalClickCondition(lhs, op, rhs);
+ } else if (!strcmp(op, "==") || !strcmp(op, ">>")) {
+ // TODO: check if >> can be used for click inputs and if == can be used for other things
+ // exact string match
+ if (lhs->_type == TEXT_INPUT) {
+ if ((rhs->_type != STRING && rhs->_type != NUMBER) || _inputText == NULL) {
+ result = false;
+ } else {
+ result = _inputText->equalsIgnoreCase(rhs->toString());
+ }
+ } else if (rhs->_type == TEXT_INPUT) {
+ if ((lhs->_type != STRING && lhs->_type != NUMBER) || _inputText == NULL) {
+ result = false;
+ } else {
+ result = _inputText->equalsIgnoreCase(lhs->toString());
+ }
+ } else {
+ error("UNHANDLED CASE: [lhs=%d/%s, rhs=%d/%s]",
+ lhs->_type, lhs->toString().c_str(), rhs->_type, rhs->toString().c_str());
+ }
+ if (!strcmp(op, ">>")) {
+ result = !result;
+ }
+
+ return result;
+ } else {
+ return evaluatePair(lhs, op, rhs);
+ }
+
+ return false;
+}
+
+Script::Operand *Script::convertOperand(Operand *operand, int type) {
+ if (operand->_type == type)
+ error("Incorrect conversion to type %d", type);
+
+ if (type == SCENE) {
+ if (operand->_type == STRING || operand->_type == NUMBER) {
+ Common::String key(operand->toString());
+ key.toLowercase();
+ if (_world->_scenes.contains(key))
+ return new Operand(_world->_scenes[key], SCENE);
+ }
+ } else if (type == OBJ) {
+ if (operand->_type == STRING || operand->_type == NUMBER) {
+ Common::String key = operand->toString();
+ key.toLowercase();
+ if (_world->_objs.contains(key))
+ return new Operand(_world->_objs[key], OBJ);
+ } else if (operand->_type == CLICK_INPUT) {
+ if (_inputClick->_classType == OBJ)
+ return new Operand(_inputClick, OBJ);
+ }
+ } else if (type == CHR) {
+ if (operand->_type == STRING || operand->_type == NUMBER) {
+ Common::String key = operand->toString();
+ key.toLowercase();
+ if (_world->_chrs.contains(key))
+ return new Operand(_world->_chrs[key], CHR);
+ } else if (operand->_type == CLICK_INPUT) {
+ if (_inputClick->_classType == CHR)
+ return new Operand(_inputClick, CHR);
+ }
+ }
+
+ return NULL;
+}
+
+bool Script::evalClickEquality(Operand *lhs, Operand *rhs, bool partialMatch) {
+ bool result = false;
+ if (lhs->_value.obj == NULL || rhs->_value.obj == NULL) {
+ result = false;
+ } else if (lhs->_value.obj == rhs->_value.obj) {
+ result = true;
+ } else if (rhs->_type == STRING) {
+ Common::String str = rhs->toString();
+ str.toLowercase();
+
+ debug(9, "evalClickEquality(%s, %s, %d)", lhs->_value.designed->_name.c_str(), rhs->_value.designed->_name.c_str(), partialMatch);
+ debug(9, "l: %d r: %d (ch: %d ob: %d)", lhs->_type, rhs->_type, CHR, OBJ);
+ debug(9, "class: %d", lhs->_value.inputClick->_classType);
+
+ if (lhs->_value.inputClick->_classType == CHR || lhs->_value.inputClick->_classType == OBJ) {
+ Common::String name = lhs->_value.designed->_name;
+ name.toLowercase();
+
+ if (partialMatch)
+ result = name.contains(str);
+ else
+ result = name.equals(str);
+ }
+
+ debug(9, "result: %d", result);
+ }
+ return result;
+}
+
+bool Script::evalClickCondition(Operand *lhs, const char *op, Operand *rhs) {
+ // TODO: check if >> can be used for click inputs
+ if (strcmp(op, "==") && strcmp(op, "=") && strcmp(op, "<") && strcmp(op, ">")) {
+ error("Unknown operation '%s' for Script::evalClickCondition", op);
+ }
+
+ bool partialMatch = strcmp(op, "==");
+ bool result;
+ if (lhs->_type == CLICK_INPUT) {
+ result = evalClickEquality(lhs, rhs, partialMatch);
+ } else {
+ result = evalClickEquality(rhs, lhs, partialMatch);
+ }
+ if (!strcmp(op, "<") || !strcmp(op, ">")) {
+ // CLICK$<FOO only matches if there was a click
+ if (_inputClick == NULL) {
+ result = false;
+ } else {
+ result = !result;
+ }
+ }
+ return result;
+}
+
+void Script::processMove() {
+ Operand *what = readOperand();
+ byte skip = _data->readByte();
+ if (skip != 0x8a)
+ error("Incorrect operator for MOVE: %02x", skip);
+
+ Operand *to = readOperand();
+
+ skip = _data->readByte();
+ if (skip != 0xfd)
+ error("No end for MOVE: %02x", skip);
+
+ evaluatePair(what, "M", to);
+
+ delete what;
+ delete to;
+}
+
+void Script::processLet() {
+ const char *lastOp = NULL;
+ int16 result = 0;
+ int operandType = _data->readByte();
+ int uservar = 0;
+
+ if (operandType == 0xff) {
+ uservar = _data->readByte();
+ }
+
+ byte eq = _data->readByte(); // skip "=" operator
+
+ debug(7, "processLet: 0x%x, uservar: 0x%x, eq: 0x%x", operandType, uservar, eq);
+
+ do {
+ Operand *operand = readOperand();
+ // TODO assert that value is NUMBER
+ int16 value = operand->_value.number;
+ delete operand;
+ if (lastOp != NULL) {
+ if (lastOp[0] == '+')
+ result += value;
+ else if (lastOp[0] == '-')
+ result -= value;
+ else if (lastOp[0] == '/')
+ result = (int16)(value == 0 ? 0 : result / value);
+ else if (lastOp[0] == '*')
+ result *= value;
+ } else {
+ result = value;
+ }
+ lastOp = readOperator();
+
+ if (lastOp[0] == ';')
+ break;
+ } while (true);
+ //System.out.println("processLet " + buildStringFromOffset(oldIndex - 1, index - oldIndex + 1) + "}");
+
+ assign(operandType, uservar, result);
+}
+
+enum {
+ BLOCK_START,
+ BLOCK_END,
+ STATEMENT,
+ OPERATOR,
+ OPCODE
+};
+
+struct Mapping {
+ const char *cmd;
+ int type;
+} static const mapping[] = {
+ { "IF{", STATEMENT }, // 0x80
+ { "=", OPERATOR },
+ { "<", OPERATOR },
+ { ">", OPERATOR },
+ { "}AND{", OPCODE },
+ { "}OR{", OPCODE },
+ { "\?\?\?(0x86)", OPCODE },
+ { "EXIT\n", BLOCK_END },
+ { "END\n", BLOCK_END }, // 0x88
+ { "MOVE{", STATEMENT },
+ { "}TO{", OPCODE },
+ { "PRINT{", STATEMENT },
+ { "SOUND{", STATEMENT },
+ { "\?\?\?(0x8d)", OPCODE },
+ { "LET{", STATEMENT },
+ { "+", OPERATOR },
+ { "-", OPERATOR }, // 0x90
+ { "*", OPERATOR },
+ { "/", OPERATOR },
+ { "==", OPERATOR },
+ { ">>", OPERATOR },
+ { "MENU{", STATEMENT },
+ { "\?\?\?(0x96)", OPCODE },
+ { "\?\?\?(0x97)", OPCODE },
+ { "\?\?\?(0x98)", OPCODE }, // 0x98
+ { "\?\?\?(0x99)", OPCODE },
+ { "\?\?\?(0x9a)", OPCODE },
+ { "\?\?\?(0x9b)", OPCODE },
+ { "\?\?\?(0x9c)", OPCODE },
+ { "\?\?\?(0x9d)", OPCODE },
+ { "\?\?\?(0x9e)", OPCODE },
+ { "\?\?\?(0x9f)", OPCODE },
+ { "TEXT$", OPCODE }, // 0xa0
+ { "CLICK$", OPCODE },
+ { "\?\?\?(0xa2)", OPCODE },
+ { "\?\?\?(0xa3)", OPCODE },
+ { "\?\?\?(0xa4)", OPCODE },
+ { "\?\?\?(0xa5)", OPCODE },
+ { "\?\?\?(0xa6)", OPCODE },
+ { "\?\?\?(0xa7)", OPCODE },
+ { "\?\?\?(0xa8)", OPCODE }, // 0xa8
+ { "\?\?\?(0xa9)", OPCODE },
+ { "\?\?\?(0xaa)", OPCODE },
+ { "\?\?\?(0xab)", OPCODE },
+ { "\?\?\?(0xac)", OPCODE },
+ { "\?\?\?(0xad)", OPCODE },
+ { "\?\?\?(0xae)", OPCODE },
+ { "\?\?\?(0xaf)", OPCODE },
+ { "VISITS#", OPCODE }, // 0xb0 // The number of scenes the player has visited, including repeated visits.
+ { "RANDOM#", OPCODE }, // RANDOM# for Star Trek, but VISITS# for some other games?
+ { "LOOP#", OPCODE }, // The number of commands the player has given in the current scene.
+ { "VICTORY#", OPCODE }, // The number of characters killed.
+ { "BADCOPY#", OPCODE },
+ { "RANDOM#", OPCODE }, // A random number between 1 and 100.
+ { "\?\?\?(0xb6)", OPCODE },
+ { "\?\?\?(0xb7)", OPCODE },
+ { "\?\?\?(0xb8)", OPCODE }, // 0xb8
+ { "\?\?\?(0xb9)", OPCODE },
+ { "\?\?\?(0xba)", OPCODE },
+ { "\?\?\?(0xbb)", OPCODE },
+ { "\?\?\?(0xbc)", OPCODE },
+ { "\?\?\?(0xbd)", OPCODE },
+ { "\?\?\?(0xbe)", OPCODE },
+ { "\?\?\?(0xbf)", OPCODE },
+ { "STORAGE@", OPCODE }, // 0xc0
+ { "SCENE@", OPCODE },
+ { "PLAYER@", OPCODE },
+ { "MONSTER@", OPCODE },
+ { "RANDOMSCN@", OPCODE },
+ { "RANDOMCHR@", OPCODE },
+ { "RANDOMOBJ@", OPCODE },
+ { "\?\?\?(0xc7)", OPCODE },
+ { "\?\?\?(0xc8)", OPCODE }, // 0xc8
+ { "\?\?\?(0xc9)", OPCODE },
+ { "\?\?\?(0xca)", OPCODE },
+ { "\?\?\?(0xcb)", OPCODE },
+ { "\?\?\?(0xcc)", OPCODE },
+ { "\?\?\?(0xcd)", OPCODE },
+ { "\?\?\?(0xce)", OPCODE },
+ { "\?\?\?(0xcf)", OPCODE },
+ { "PHYS.STR.BAS#", OPCODE }, // 0xd0
+ { "PHYS.HIT.BAS#", OPCODE },
+ { "PHYS.ARM.BAS#", OPCODE },
+ { "PHYS.ACC.BAS#", OPCODE },
+ { "SPIR.STR.BAS#", OPCODE },
+ { "SPIR.HIT.BAS#", OPCODE },
+ { "SPIR.ARM.BAS#", OPCODE },
+ { "SPIR.ACC.BAS#", OPCODE },
+ { "PHYS.SPE.BAS#", OPCODE }, // 0xd8
+ { "\?\?\?(0xd9)", OPCODE },
+ { "\?\?\?(0xda)", OPCODE },
+ { "\?\?\?(0xdb)", OPCODE },
+ { "\?\?\?(0xdc)", OPCODE },
+ { "\?\?\?(0xdd)", OPCODE },
+ { "\?\?\?(0xde)", OPCODE },
+ { "\?\?\?(0xdf)", OPCODE },
+ { "PHYS.STR.CUR#", OPCODE }, // 0xe0
+ { "PHYS.HIT.CUR#", OPCODE },
+ { "PHYS.ARM.CUR#", OPCODE },
+ { "PHYS.ACC.CUR#", OPCODE },
+ { "SPIR.STR.CUR#", OPCODE },
+ { "SPIR.HIT.CUR#", OPCODE },
+ { "SPIR.ARM.CUR#", OPCODE },
+ { "SPIR.ACC.CUR#", OPCODE },
+ { "PHYS.SPE.CUR#", OPCODE }, // 0xe8
+ { "\?\?\?(0xe9)", OPCODE },
+ { "\?\?\?(0xea)", OPCODE },
+ { "\?\?\?(0xeb)", OPCODE },
+ { "\?\?\?(0xec)", OPCODE },
+ { "\?\?\?(0xed)", OPCODE },
+ { "\?\?\?(0xee)", OPCODE },
+ { "\?\?\?(0xef)", OPCODE },
+ { "\?\?\?(0xf0)", OPCODE },
+ { "\?\?\?(0xf1)", OPCODE },
+ { "\?\?\?(0xf2)", OPCODE },
+ { "\?\?\?(0xf3)", OPCODE },
+ { "\?\?\?(0xf4)", OPCODE },
+ { "\?\?\?(0xf5)", OPCODE },
+ { "\?\?\?(0xf6)", OPCODE },
+ { "\?\?\?(0xf7)", OPCODE },
+ { "\?\?\?(0xf8)", OPCODE }, // 0xa8
+ { "\?\?\?(0xf9)", OPCODE },
+ { "\?\?\?(0xfa)", OPCODE },
+ { "\?\?\?(0xfb)", OPCODE },
+ { "\?\?\?(0xfc)", OPCODE },
+ { "}\n", OPCODE },
+ { "}THEN\n", BLOCK_START },
+ { "\?\?\?(0xff)", OPCODE } // Uservar
+};
+
+void Script::convertToText() {
+ _data->seek(12);
+
+ int indentLevel = 0;
+ ScriptText *scr = new ScriptText;
+ scr->offset = _data->pos();
+
+ while(true) {
+ int c = _data->readByte();
+
+ if (_data->eos())
+ break;
+
+ if (c < 0x80) {
+ if (c < 0x20)
+ error("Unknown code 0x%02x at %d", c, _data->pos());
+
+ do {
+ scr->line += c;
+ c = _data->readByte();
+ } while (c < 0x80);
+
+ _data->seek(-1, SEEK_CUR);
+ } else if (c == 0xff) {
+ int value = _data->readByte();
+ value -= 1;
+ scr->line += (char)('A' + (value / 9));
+ scr->line += (char)('0' + (value % 9) + 1);
+ scr->line += '#';
+ } else {
+ const char *cmd = mapping[c - 0x80].cmd;
+ int type = mapping[c - 0x80].type;
+
+ if (type == STATEMENT) {
+ for (int i = 0; i < indentLevel; i++)
+ scr->line += ' ';
+ } else if (type == BLOCK_START) {
+ indentLevel += 2;
+ } else if (type == BLOCK_END) {
+ indentLevel -= 2;
+ for (int i = 0; i < indentLevel; i++)
+ scr->line += ' ';
+ }
+
+ scr->line += cmd;
+
+ if (strchr(cmd, '\n')) {
+ scr->line.deleteLastChar();
+
+ _scriptText.push_back(scr);
+
+ scr = new ScriptText;
+ scr->offset = _data->pos();
+ }
+ }
+ }
+
+ if (!scr->line.empty())
+ _scriptText.push_back(scr);
+ else
+ delete scr;
+}
+
+} // End of namespace Wage
diff --git a/engines/wage/script.h b/engines/wage/script.h
new file mode 100644
index 0000000000..1a7d07d29a
--- /dev/null
+++ b/engines/wage/script.h
@@ -0,0 +1,153 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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);
+ ~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;
+ _type = type;
+ }
+
+ Operand(Chr *value, OperandType type) {
+ _value.chr = value;
+ _type = type;
+ }
+
+ Operand(Scene *value, OperandType type) {
+ _value.scene = value;
+ _type = type;
+ }
+
+ Operand(int value, OperandType type) {
+ _value.number = value;
+ _type = type;
+ }
+
+ Operand(Common::String *value, OperandType type) {
+ _value.string = value;
+ _type = type;
+ }
+
+ Operand(Designed *value, OperandType type) {
+ _value.inputClick = value;
+ _type = type;
+ }
+
+ ~Operand() {
+ if (_type == STRING)
+ delete _value.string;
+ }
+
+ Common::String toString();
+ };
+
+ 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, WageEngine *engine);
+
+private:
+ 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);
+
+ Common::Array<ScriptText *> _scriptText;
+ void convertToText();
+};
+
+} // End of namespace Wage
+
+#endif
diff --git a/engines/wage/util.cpp b/engines/wage/util.cpp
new file mode 100644
index 0000000000..1b3dfc9452
--- /dev/null
+++ b/engines/wage/util.cpp
@@ -0,0 +1,123 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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::String readPascalString(Common::SeekableReadStream *in) {
+ Common::String s;
+ char *buf;
+ int len;
+ int i;
+
+ len = in->readByte();
+ buf = (char *)malloc(len + 1);
+ for (i = 0; i < len; i++) {
+ buf[i] = in->readByte();
+ if (buf[i] == 0x0d)
+ buf[i] = '\n';
+ }
+
+ buf[i] = 0;
+
+ s = buf;
+ free(buf);
+
+ return s;
+}
+
+Common::Rect *readRect(Common::SeekableReadStream *in) {
+ int x1, y1, x2, y2;
+
+ y1 = in->readUint16BE();
+ x1 = in->readUint16BE();
+ y2 = in->readUint16BE() + 4;
+ x2 = in->readUint16BE() + 4;
+
+ 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 ";
+ }
+ 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";
+}
+
+} // End of namespace Wage
diff --git a/engines/wage/wage.cpp b/engines/wage/wage.cpp
new file mode 100644
index 0000000000..1c13a31f66
--- /dev/null
+++ b/engines/wage/wage.cpp
@@ -0,0 +1,504 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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/debug-channels.h"
+
+#include "engines/engine.h"
+#include "engines/util.h"
+#include "gui/EventRecorder.h"
+
+#include "wage/wage.h"
+#include "wage/entities.h"
+#include "wage/gui.h"
+#include "wage/dialog.h"
+#include "wage/script.h"
+#include "wage/world.h"
+
+namespace Wage {
+
+WageEngine::WageEngine(OSystem *syst, const ADGameDescription *desc) : Engine(syst), _gameDescription(desc) {
+ _rnd = new Common::RandomSource("wage");
+
+ _aim = -1;
+ _opponentAim = -1;
+ _temporarilyHidden = false;
+ _isGameOver = false;
+ _monster = NULL;
+ _running = NULL;
+ _lastScene = NULL;
+
+ _commandWasQuick = false;
+
+ _shouldQuit = false;
+
+ debug("WageEngine::WageEngine()");
+}
+
+WageEngine::~WageEngine() {
+ debug("WageEngine::~WageEngine()");
+
+ DebugMan.clearAllDebugChannels();
+ delete _world;
+ delete _resManager;
+ delete _gui;
+ delete _rnd;
+ delete _console;
+}
+
+Common::Error WageEngine::run() {
+ initGraphics(512, 342, true);
+
+ // Create debugger console. It requires GFX to be initialized
+ _console = new Console(this);
+
+ debug("WageEngine::init");
+
+ // Your main event loop should be (invoked from) here.
+ _resManager = new Common::MacResManager();
+ _resManager->open(getGameFile());
+
+ _world = new World(this);
+
+ if (!_world->loadWorld(_resManager))
+ return Common::kNoGameDataFoundError;
+
+ _gui = new Gui(this);
+
+ _temporarilyHidden = true;
+ performInitialSetup();
+ Common::String input("look");
+ processTurn(&input, NULL);
+ _temporarilyHidden = false;
+
+ _shouldQuit = false;
+
+ while (!_shouldQuit) {
+ processEvents();
+
+ _gui->draw();
+ g_system->updateScreen();
+ g_system->delayMillis(50);
+ }
+
+ //_world->_orderedScenes[1]->_design->paint(&screen, _world->_patterns, false);
+ //_world->_objs["frank.1"]->_design->setBounds(&r);
+ //_world->_objs["frank.1"]->_design->paint(&screen, _world->_patterns, false);
+ //_world->_scenes["temple of the holy mackeral"]->_design->setBounds(&r);
+ //_world->_scenes["temple of the holy mackeral"]->_design->paint(&screen, _world->_patterns, false);
+ //_world->_scenes["tower level 3"]->_design->setBounds(&r);
+ //_world->_scenes["tower level 3"]->_design->paint(&screen, _world->_patterns, false);
+
+ return Common::kNoError;
+}
+
+void WageEngine::processEvents() {
+ Common::Event event;
+
+ while (_eventMan->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_QUIT:
+ if (saveDialog())
+ _shouldQuit = true;
+ break;
+ case Common::EVENT_MOUSEMOVE:
+ _gui->mouseMove(event.mouse.x, event.mouse.y);
+ break;
+ case Common::EVENT_LBUTTONDOWN:
+ _gui->mouseDown(event.mouse.x, event.mouse.y);
+ break;
+ case Common::EVENT_LBUTTONUP:
+ {
+ Designed *obj = _gui->mouseUp(event.mouse.x, event.mouse.y);
+ if (obj != NULL)
+ processTurn(NULL, obj);
+ }
+ break;
+ case Common::EVENT_KEYDOWN:
+ switch (event.kbd.keycode) {
+ case Common::KEYCODE_BACKSPACE:
+ if (!_inputText.empty()) {
+ _inputText.deleteLastChar();
+ _gui->drawInput();
+ }
+ break;
+
+ case Common::KEYCODE_RETURN:
+ if (_inputText.empty())
+ break;
+
+ processTurn(&_inputText, NULL);
+ _inputText.clear();
+ _gui->appendText("");
+ _gui->disableUndo();
+ break;
+
+ default:
+ if (event.kbd.flags & (Common::KBD_ALT | Common::KBD_CTRL | Common::KBD_META)) {
+ if (event.kbd.ascii >= 0x20 && event.kbd.ascii <= 0x7f) {
+ _gui->processMenuShortCut(event.kbd.flags, event.kbd.ascii);
+ }
+ break;
+ }
+
+ if (event.kbd.ascii >= 0x20 && event.kbd.ascii <= 0x7f) {
+ _inputText += (char)event.kbd.ascii;
+ _gui->drawInput();
+ }
+
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+void WageEngine::playSound(Common::String soundName) {
+ warning("STUB: WageEngine::playSound(%s)", soundName.c_str());
+}
+
+void WageEngine::setMenu(Common::String menu) {
+ _world->_commandsMenu = menu;
+
+ _gui->regenCommandsMenu();
+}
+
+void WageEngine::appendText(const char *str) {
+ _gui->appendText(str);
+
+ _inputText.clear();
+}
+
+void WageEngine::gameOver() {
+ DialogButtonArray buttons;
+
+ buttons.push_back(new DialogButton("OK", 66, 67, 68, 28));
+
+ Dialog gameOver(_gui, 199, _world->_gameOverMessage->c_str(), &buttons, 0);
+
+ gameOver.run();
+
+ doClose();
+}
+
+bool WageEngine::saveDialog() {
+ DialogButtonArray buttons;
+
+ buttons.push_back(new DialogButton("No", 19, 67, 68, 28));
+ buttons.push_back(new DialogButton("Yes", 112, 67, 68, 28));
+ buttons.push_back(new DialogButton("Cancel", 205, 67, 68, 28));
+
+ Dialog save(_gui, 291, "Save changes before closing?", &buttons, 1);
+
+ int button = save.run();
+
+ if (button == 2) // Cancel
+ return false;
+
+ if (button == 1)
+ saveGame();
+
+ doClose();
+
+ return true;
+}
+
+void WageEngine::saveGame() {
+ warning("STUB: saveGame()");
+}
+
+void WageEngine::performInitialSetup() {
+ debug(5, "Resetting Objs: %d", _world->_orderedObjs.size());
+ 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());
+ 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 (!obj->_sceneOrOwner.equalsIgnoreCase(STORAGESCENE)) {
+ Common::String location = obj->_sceneOrOwner;
+ location.toLowercase();
+ if (_world->_scenes.contains(location)) {
+ _world->move(obj, _world->_scenes[location]);
+ } 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 (!chr->_initialScene.equalsIgnoreCase(STORAGESCENE)) {
+ 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());
+ }
+}
+
+void WageEngine::doClose() {
+ warning("STUB: doClose()");
+}
+
+Scene *WageEngine::getSceneByName(Common::String &location) {
+ Scene *scene;
+ if (location.equals("random@")) {
+ scene = _world->getRandomScene();
+ } else {
+ scene = _world->_scenes[location];
+ }
+ return scene;
+}
+
+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;
+ 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;
+ 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;
+ }
+ }
+ } else if (to == player->_currentScene) {
+ if (getMonster() == NULL) {
+ _monster = chr;
+ encounter(player, chr);
+ }
+ }
+ }
+ if (!_temporarilyHidden) {
+ if (to == currentScene || from == currentScene) {
+ redrawScene();
+ g_system->delayMillis(100);
+ }
+ }
+}
+
+void WageEngine::redrawScene() {
+ Scene *currentScene = _world->_player->_currentScene;
+
+ if (currentScene != NULL) {
+ bool firstTime = (_lastScene != currentScene);
+
+ updateSoundTimerForScene(currentScene, firstTime);
+ }
+}
+
+void WageEngine::updateSoundTimerForScene(Scene *scene, bool firstTime) {
+ //warning("STUB: WageEngine::updateSoundTimerForScene()");
+}
+
+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, this);
+
+ playerScene = _world->_player->_currentScene;
+
+ if (playerScene == _world->_storageScene)
+ return;
+
+ if (playerScene != _lastScene) {
+ _temporarilyHidden = true;
+ _gui->clearOutput();
+ regen();
+ Common::String input("look");
+ processTurnInternal(&input, NULL);
+ 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 = getMonster();
+ Common::String input;
+
+ if (textInput)
+ input = *textInput;
+
+ input.toLowercase();
+ if (input.equals("e"))
+ input = "east";
+ else if (input.equals("w"))
+ input = "west";
+ else if (input.equals("n"))
+ input = "north";
+ else if (input.equals("s"))
+ input = "south";
+
+ processTurnInternal(&input, clickInput);
+ Scene *playerScene = _world->_player->_currentScene;
+
+ if (prevScene != playerScene && playerScene != _world->_storageScene) {
+ if (prevMonster != NULL) {
+ bool followed = false;
+ if (getMonster() == NULL) {
+ // TODO: adjacent scenes doesn't contain up/down etc... verify that monsters can't follow these...
+ 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 {
+ snprintf(buf, 512, "You escape %s%s.", prevMonster->getDefiniteArticle(false), prevMonster->_name.c_str());
+ appendText(buf);
+ }
+ }
+ }
+ if (!_commandWasQuick && getMonster() != NULL) {
+ performCombatAction(getMonster(), _world->_player);
+ }
+}
+
+
+} // End of namespace Wage
diff --git a/engines/wage/wage.h b/engines/wage/wage.h
new file mode 100644
index 0000000000..6905fdc530
--- /dev/null
+++ b/engines/wage/wage.h
@@ -0,0 +1,234 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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 "common/debug.h"
+#include "gui/debugger.h"
+#include "common/endian.h"
+#include "common/rect.h"
+#include "common/macresman.h"
+#include "common/random.h"
+
+struct ADGameDescription;
+
+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;
+
+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
+};
+
+// our engine debug levels
+enum {
+ kWageDebugExample = 1 << 0,
+ kWageDebugExample2 = 1 << 1
+ // next new level must be 1 << 2 (4)
+ // the current limitation is 32 debug levels (1 << 31 is the last one)
+};
+
+enum {
+ kColorBlack = 0,
+ kColorGray = 1,
+ kColorWhite = 2,
+ kColorGreen = 3
+};
+
+Common::String readPascalString(Common::SeekableReadStream *in);
+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);
+
+
+typedef Common::Array<byte *> Patterns;
+
+class WageEngine : public Engine {
+ friend class Dialog;
+public:
+ WageEngine(OSystem *syst, const ADGameDescription *gameDesc);
+ ~WageEngine();
+
+ virtual bool hasFeature(EngineFeature f) const;
+
+ virtual Common::Error run();
+
+ bool canLoadGameStateCurrently();
+ bool canSaveGameStateCurrently();
+
+ const char *getGameFile() const;
+ void processTurn(Common::String *textInput, Designed *clickInput);
+ void regen();
+
+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);
+
+ void doClose();
+ void updateSoundTimerForScene(Scene *scene, bool firstTime);
+
+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);
+
+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;
+
+ Common::String _inputText;
+
+ void playSound(Common::String soundName);
+ void setMenu(Common::String soundName);
+ void appendText(const char *str);
+ void gameOver();
+ bool saveDialog();
+ 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();
+
+private:
+ Console *_console;
+
+ const ADGameDescription *_gameDescription;
+
+ Common::MacResManager *_resManager;
+
+ bool _shouldQuit;
+};
+
+// Example console class
+class Console : public GUI::Debugger {
+public:
+ Console(WageEngine *vm) {}
+ virtual ~Console(void) {}
+};
+
+} // End of namespace Wage
+
+#endif
diff --git a/engines/wage/world.cpp b/engines/wage/world.cpp
new file mode 100644
index 0000000000..a387d95c4a
--- /dev/null
+++ b/engines/wage/world.cpp
@@ -0,0 +1,510 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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 "wage/wage.h"
+#include "wage/entities.h"
+#include "wage/script.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;
+
+ _engine = engine;
+}
+
+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(_patterns[i]);
+
+ 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;
+
+ 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);
+
+ // TODO: read creator
+
+ // Load main configuration
+ if ((resArray = resMan->getResIDArray(MKTAG('V','E','R','S'))).size() == 0)
+ return false;
+
+ _name = resMan->getBaseFileName();
+
+ 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]);
+
+ res->skip(10);
+ byte b = res->readByte();
+ _weaponMenuDisabled = (b != 0);
+ if (b != 0 && b != 1)
+ error("Unexpected value for weapons menu");
+
+ res->skip(3);
+ _aboutMessage = readPascalString(res);
+
+ if (!scumm_stricmp(resMan->getBaseFileName().c_str(), "Scepters"))
+ res->skip(1); // ????
+
+ _soundLibrary1 = readPascalString(res);
+ _soundLibrary2 = readPascalString(res);
+
+ delete res;
+ }
+
+ Common::String *message;
+ if ((message = loadStringFromDITL(resMan, 2910, 1)) != NULL) {
+ message->trim();
+ debug(2, "_gameOverMessage: %s", message->c_str());
+ _gameOverMessage = message;
+ }
+ if ((message = loadStringFromDITL(resMan, 2480, 3)) != NULL) {
+ message->trim();
+ debug(2, "_saveBeforeQuitMessage: %s", message->c_str());
+ _saveBeforeQuitMessage = message;
+ }
+ if ((message = loadStringFromDITL(resMan, 2490, 3)) != NULL) {
+ message->trim();
+ debug(2, "_saveBeforeCloseMessage: %s", message->c_str());
+ _saveBeforeCloseMessage = message;
+ }
+ if ((message = loadStringFromDITL(resMan, 2940, 2)) != NULL) {
+ message->trim();
+ debug(2, "_revertMessage: %s", message->c_str());
+ _revertMessage = message;
+ }
+
+ // 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);
+
+ res = resMan->getResource(MKTAG('A','T','X','T'), *iter);
+ if (res != NULL) {
+ scene->_textBounds = readRect(res);
+ scene->_fontType = res->readUint16BE();
+ scene->_fontSize = res->readUint16BE();
+
+ Common::String text;
+ while (res->pos() < res->size()) {
+ char c = res->readByte();
+ if (c == 0x0d)
+ c = '\n';
+ text += c;
+ }
+ scene->_text = text;
+
+ delete res;
+ }
+ 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));
+ }
+
+ // 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);
+
+ addChr(chr);
+ // TODO: What if there's more than one player character?
+ if (chr->_playerCharacter)
+ _player = chr;
+ }
+
+ // 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) {
+ res->skip(0x55ac);
+ 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 = readMenu(res);
+ _aboutMenuItemName.clear();
+ Common::String string = menu->operator[](1);
+
+ for (uint i = 0; i < string.size() && string[i] != ';'; i++) // Read token
+ _aboutMenuItemName += string[i];
+
+ delete res;
+ }
+ res = resMan->getResource(MKTAG('M','E','N','U'), 2004);
+ if (res != NULL) {
+ Common::StringArray *menu = readMenu(res);
+ _commandsMenuName = menu->operator[](0);
+ _commandsMenu = menu->operator[](1);
+ delete menu;
+ delete res;
+ }
+ res = resMan->getResource(MKTAG('M','E','N','U'), 2005);
+ if (res != NULL) {
+ Common::StringArray *menu = readMenu(res);
+ _weaponsMenuName = menu->operator[](0);
+ delete menu;
+ delete res;
+ }
+ // 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;
+}
+
+Common::StringArray *World::readMenu(Common::SeekableReadStream *res) {
+ res->skip(10);
+ int enableFlags = res->readUint32BE();
+ Common::String menuName = readPascalString(res);
+ Common::String menuItem = readPascalString(res);
+ int menuItemNumber = 1;
+ Common::String menu;
+ byte itemData[4];
+
+ while (!menuItem.empty()) {
+ if (!menu.empty()) {
+ menu += ';';
+ }
+ if ((enableFlags & (1 << menuItemNumber)) == 0) {
+ menu += '(';
+ }
+ menu += menuItem;
+ res->read(itemData, 4);
+ static const char styles[] = {'B', 'I', 'U', 'O', 'S', 'C', 'E', 0};
+ for (int i = 0; styles[i] != 0; i++) {
+ if ((itemData[3] & (1 << i)) != 0) {
+ menu += '<';
+ menu += styles[i];
+ }
+ }
+ if (itemData[1] != 0) {
+ menu += '/';
+ menu += (char)itemData[1];
+ }
+ menuItem = readPascalString(res);
+ menuItemNumber++;
+ }
+
+ Common::StringArray *result = new Common::StringArray;
+ result->push_back(menuName);
+ result->push_back(menu);
+
+ debug(4, "menuName: %s", menuName.c_str());
+ debug(4, "menu: %s", menu.c_str());
+
+ return result;
+}
+
+void World::loadExternalSounds(Common::String fname) {
+ Common::File in;
+
+ in.open(fname);
+ if (!in.isOpen()) {
+ warning("Cannot load sound file <%s>", fname.c_str());
+ return;
+ }
+ in.close();
+
+ Common::MacResManager *resMan;
+ resMan = new Common::MacResManager();
+ resMan->open(fname);
+
+ 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);
+ 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 = readPascalString(res);
+ 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->_currentOwner = 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->_currentScene = 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()) {
+ sprintf(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));
+ strcat(menu, _name.c_str());
+ strcat(menu, pos + 1);
+ }
+ }
+
+ return menu;
+}
+
+} // End of namespace Wage
diff --git a/engines/wage/world.h b/engines/wage/world.h
new file mode 100644
index 0000000000..1416fc39e6
--- /dev/null
+++ b/engines/wage/world.h
@@ -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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * 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
+
+namespace Wage {
+
+#define STORAGESCENE "STORAGE@"
+
+class World {
+public:
+ World(WageEngine *engine);
+ ~World();
+
+ bool loadWorld(Common::MacResManager *resMan);
+ void loadExternalSounds(Common::String 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::String _soundLibrary1;
+ Common::String _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;
+ Patterns _patterns;
+ Scene *_storageScene;
+ Chr *_player;
+ //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 _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) {
+ Common::String s = sound->_name;
+ s.toLowercase();
+ _sounds[s] = sound;
+ _orderedSounds.push_back(sound);
+ }
+
+private:
+ Common::StringArray *readMenu(Common::SeekableReadStream *res);
+};
+
+} // End of namespace Wage
+
+#endif
diff --git a/graphics/primitives.cpp b/graphics/primitives.cpp
index 564bdb9673..90009b3332 100644
--- a/graphics/primitives.cpp
+++ b/graphics/primitives.cpp
@@ -20,7 +20,9 @@
*
*/
+#include "common/algorithm.h"
#include "common/util.h"
+#include "graphics/primitives.h"
namespace Graphics {
@@ -62,6 +64,22 @@ void drawLine(int x0, int y0, int x1, int y1, int color, void (*plotProc)(int, i
}
}
+void drawHLine(int x1, int x2, int y, int color, void (*plotProc)(int, int, int, void *), void *data) {
+ if (x1 > x2)
+ SWAP(x1, x2);
+
+ for (int x = x1; x <= x2; x++)
+ (*plotProc)(x, y, color, data);
+}
+
+void drawVLine(int x, int y1, int y2, int color, void (*plotProc)(int, int, int, void *), void *data) {
+ if (y1 > y2)
+ SWAP(y1, y2);
+
+ for (int y = y1; y <= y2; y++)
+ (*plotProc)(x, y, color, data);
+}
+
void drawThickLine(int x0, int y0, int x1, int y1, int penX, int penY, int color, void (*plotProc)(int, int, int, void *), void *data) {
assert(penX > 0 && penY > 0);
@@ -79,4 +97,335 @@ void drawThickLine(int x0, int y0, int x1, int y1, int penX, int penY, int color
drawLine(x0 + x, y0 + y, x1 + x, y1 + y, color, plotProc, data);
}
+/* Bresenham as presented in Foley & Van Dam */
+/* Code is based on GD lib http://libgd.github.io/ */
+void drawThickLine2(int x1, int y1, int x2, int y2, int thick, int color, void (*plotProc)(int, int, int, void *), void *data) {
+ int incr1, incr2, d, x, y, xend, yend, xdirflag, ydirflag;
+ int wid;
+ int w, wstart;
+
+ int dx = abs(x2 - x1);
+ int dy = abs(y2 - y1);
+
+ if (dx == 0) {
+ if (y1 > y2)
+ SWAP(y1, y2);
+ Common::Rect r(x1, y1, x1 + thick - 1, y2);
+ drawFilledRect(r, color, plotProc, data);
+ return;
+ } else if (dy == 0) {
+ if (x1 > x2)
+ SWAP(x1, x2);
+ Common::Rect r(x1, y1, x2, y1 + thick - 1);
+ drawFilledRect(r, color, plotProc, data);
+ return;
+ }
+
+ if (dy <= dx) {
+ /* More-or-less horizontal. use wid for vertical stroke */
+ /* Doug Claar: watch out for NaN in atan2 (2.0.5) */
+
+ /* 2.0.12: Michael Schwartz: divide rather than multiply;
+ TBB: but watch out for /0! */
+ double ac = cos(atan2 (dy, dx));
+ if (ac != 0) {
+ wid = thick / ac;
+ } else {
+ wid = 1;
+ }
+ if (wid == 0) {
+ wid = 1;
+ }
+ d = 2 * dy - dx;
+ incr1 = 2 * dy;
+ incr2 = 2 * (dy - dx);
+ if (x1 > x2) {
+ x = x2;
+ y = y2;
+ ydirflag = (-1);
+ xend = x1;
+ } else {
+ x = x1;
+ y = y1;
+ ydirflag = 1;
+ xend = x2;
+ }
+
+ /* Set up line thickness */
+ wstart = y - wid / 2;
+ for (w = wstart; w < wstart + wid; w++)
+ (*plotProc)(x, y, color, data);
+
+ if (((y2 - y1) * ydirflag) > 0) {
+ while (x < xend) {
+ x++;
+ if (d < 0) {
+ d += incr1;
+ } else {
+ y++;
+ d += incr2;
+ }
+ wstart = y - wid / 2;
+ for (w = wstart; w < wstart + wid; w++)
+ (*plotProc)(x, w, color, data);
+ }
+ } else {
+ while (x < xend) {
+ x++;
+ if (d < 0) {
+ d += incr1;
+ } else {
+ y--;
+ d += incr2;
+ }
+ wstart = y - wid / 2;
+ for (w = wstart; w < wstart + wid; w++)
+ (*plotProc)(x, w, color, data);
+ }
+ }
+ } else {
+ /* More-or-less vertical. use wid for horizontal stroke */
+ /* 2.0.12: Michael Schwartz: divide rather than multiply;
+ TBB: but watch out for /0! */
+ double as = sin(atan2(dy, dx));
+ if (as != 0) {
+ wid = thick / as;
+ } else {
+ wid = 1;
+ }
+ if (wid == 0)
+ wid = 1;
+
+ d = 2 * dx - dy;
+ incr1 = 2 * dx;
+ incr2 = 2 * (dx - dy);
+ if (y1 > y2) {
+ y = y2;
+ x = x2;
+ yend = y1;
+ xdirflag = (-1);
+ } else {
+ y = y1;
+ x = x1;
+ yend = y2;
+ xdirflag = 1;
+ }
+
+ /* Set up line thickness */
+ wstart = x - wid / 2;
+ for (w = wstart; w < wstart + wid; w++)
+ (*plotProc)(w, y, color, data);
+
+ if (((x2 - x1) * xdirflag) > 0) {
+ while (y < yend) {
+ y++;
+ if (d < 0) {
+ d += incr1;
+ } else {
+ x++;
+ d += incr2;
+ }
+ wstart = x - wid / 2;
+ for (w = wstart; w < wstart + wid; w++)
+ (*plotProc)(w, y, color, data);
+ }
+ } else {
+ while (y < yend) {
+ y++;
+ if (d < 0) {
+ d += incr1;
+ } else {
+ x--;
+ d += incr2;
+ }
+ wstart = x - wid / 2;
+ for (w = wstart; w < wstart + wid; w++)
+ (*plotProc)(w, y, color, data);
+ }
+ }
+ }
+}
+
+void drawFilledRect(Common::Rect &rect, int color, void (*plotProc)(int, int, int, void *), void *data) {
+ for (int y = rect.top; y <= rect.bottom; y++)
+ drawHLine(rect.left, rect.right, y, color, plotProc, data);
+}
+
+// http://members.chello.at/easyfilter/bresenham.html
+void drawRoundRect(Common::Rect &rect, int arc, int color, bool filled, void (*plotProc)(int, int, int, void *), void *data) {
+ if (rect.height() < rect.width()) {
+ int x = -arc, y = 0, err = 2-2*arc; /* II. Quadrant */
+ int dy = rect.height() - arc * 2;
+ int r = arc;
+ int stop = 0;
+ int lastx, lasty;
+ if (dy < 0)
+ stop = -dy / 2;
+
+ do {
+ if (filled) {
+ drawHLine(rect.left+x+r, rect.right-x-r, rect.top-y+r-stop, color, plotProc, data);
+ drawHLine(rect.left+x+r, rect.right-x-r, rect.bottom+y-r+stop, color, plotProc, data);
+ } else {
+ (*plotProc)(rect.left+x+r, rect.top-y+r-stop, color, data);
+ (*plotProc)(rect.right-x-r, rect.top-y+r-stop, color, data);
+ (*plotProc)(rect.left+x+r, rect.bottom+y-r+stop, color, data);
+ (*plotProc)(rect.right-x-r, rect.bottom+y-r+stop, color, data);
+
+ lastx = x;
+ lasty = y;
+ }
+ arc = err;
+ if (arc <= y) err += ++y*2+1; /* e_xy+e_y < 0 */
+ if (arc > x || err > y) err += ++x*2+1; /* e_xy+e_x > 0 or no 2nd y-step */
+ if (stop && y > stop)
+ break;
+ } while (x < 0);
+
+ if (!filled) {
+ x = lastx;
+ y = lasty;
+
+ drawHLine(rect.left+x+r, rect.right-x-r, rect.top-y+r-stop, color, plotProc, data);
+ drawHLine(rect.left+x+r, rect.right-x-r, rect.bottom+y-r+stop, color, plotProc, data);
+ }
+
+ for (int i = 0; i < dy; i++) {
+ if (filled) {
+ drawHLine(rect.left, rect.right, rect.top + r + i, color, plotProc, data);
+ } else {
+ (*plotProc)(rect.left, rect.top + r + i, color, data);
+ (*plotProc)(rect.right, rect.top + r + i, color, data);
+ }
+ }
+ } else {
+ int y = -arc, x = 0, err = 2-2*arc; /* II. Quadrant */
+ int dx = rect.width() - arc * 2;
+ int r = arc;
+ int stop = 0;
+ int lastx, lasty;
+ if (dx < 0)
+ stop = -dx / 2;
+
+ do {
+ if (filled) {
+ drawVLine(rect.left-x+r-stop, rect.top+y+r, rect.bottom-y-r, color, plotProc, data);
+ drawVLine(rect.right+x-r+stop, rect.top+y+r, rect.bottom-y-r, color, plotProc, data);
+ } else {
+ (*plotProc)(rect.left-x+r-stop, rect.top+y+r, color, data);
+ (*plotProc)(rect.left-x+r-stop, rect.bottom-y-r, color, data);
+ (*plotProc)(rect.right+x-r+stop, rect.top+y+r, color, data);
+ (*plotProc)(rect.right+x-r+stop, rect.bottom-y-r, color, data);
+
+ lastx = x;
+ lasty = y;
+ }
+
+ arc = err;
+ if (arc <= x) err += ++x*2+1; /* e_xy+e_y < 0 */
+ if (arc > y || err > x) err += ++y*2+1; /* e_xy+e_x > 0 or no 2nd y-step */
+ if (stop && x > stop)
+ break;
+ } while (y < 0);
+
+ if (!filled) {
+ x = lastx;
+ y = lasty;
+ drawVLine(rect.left-x+r-stop, rect.top+y+r, rect.bottom-y-r, color, plotProc, data);
+ drawVLine(rect.right+x-r+stop, rect.top+y+r, rect.bottom-y-r, color, plotProc, data);
+ }
+
+ for (int i = 0; i < dx; i++) {
+ if (filled) {
+ drawVLine(rect.left + r + i, rect.top, rect.bottom, color, plotProc, data);
+ } else {
+ (*plotProc)(rect.left + r + i, rect.top, color, data);
+ (*plotProc)(rect.left + r + i, rect.bottom, color, data);
+ }
+ }
+ }
+}
+
+// Based on public-domain code by Darel Rex Finley, 2007
+// http://alienryderflex.com/polygon_fill/
+void drawPolygonScan(int *polyX, int *polyY, int npoints, Common::Rect &bbox, int color, void (*plotProc)(int, int, int, void *), void *data) {
+ int *nodeX = (int *)calloc(npoints, sizeof(int));
+ int i, j;
+
+ // Loop through the rows of the image.
+ for (int pixelY = bbox.top; pixelY < bbox.bottom; pixelY++) {
+ // Build a list of nodes.
+ int nodes = 0;
+ j = npoints - 1;
+
+ for (i = 0; i < npoints; i++) {
+ if ((polyY[i] < pixelY && polyY[j] >= pixelY) || (polyY[j] < pixelY && polyY[i] >= pixelY)) {
+ nodeX[nodes++] = (int)(polyX[i] + (double)(pixelY - polyY[i]) / (double)(polyY[j]-polyY[i]) *
+ (double)(polyX[j] - polyX[i]) + 0.5);
+ }
+ j = i;
+ }
+
+ // Sort the nodes
+ Common::sort(nodeX, &nodeX[nodes]);
+
+ // Fill the pixels between node pairs.
+ for (i = 0; i < nodes; i += 2) {
+ if (nodeX[i ] >= bbox.right)
+ break;
+ if (nodeX[i + 1] > bbox.left) {
+ nodeX[i] = MAX<int16>(nodeX[i], bbox.left);
+ nodeX[i + 1] = MIN<int16>(nodeX[i + 1], bbox.right);
+
+ drawHLine(nodeX[i], nodeX[i + 1], pixelY, color, plotProc, data);
+ }
+ }
+ }
+
+ free(nodeX);
+}
+
+// http://members.chello.at/easyfilter/bresenham.html
+void drawEllipse(int x0, int y0, int x1, int y1, int color, bool filled, void (*plotProc)(int, int, int, void *), void *data) {
+ int a = abs(x1-x0), b = abs(y1-y0), b1 = b&1; /* values of diameter */
+ long dx = 4*(1-a)*b*b, dy = 4*(b1+1)*a*a; /* error increment */
+ long err = dx+dy+b1*a*a, e2; /* error of 1.step */
+
+ if (x0 > x1) { x0 = x1; x1 += a; } /* if called with swapped points */
+ if (y0 > y1) y0 = y1; /* .. exchange them */
+ y0 += (b+1)/2; y1 = y0-b1; /* starting pixel */
+ a *= 8*a; b1 = 8*b*b;
+
+ do {
+ if (filled) {
+ drawHLine(x0, x1, y0, color, plotProc, data);
+ drawHLine(x0, x1, y1, color, plotProc, data);
+ } else {
+ (*plotProc)(x1, y0, color, data); /* I. Quadrant */
+ (*plotProc)(x0, y0, color, data); /* II. Quadrant */
+ (*plotProc)(x0, y1, color, data); /* III. Quadrant */
+ (*plotProc)(x1, y1, color, data); /* IV. Quadrant */
+ }
+ e2 = 2*err;
+ if (e2 <= dy) { y0++; y1--; err += dy += a; } /* y step */
+ if (e2 >= dx || 2*err > dy) { x0++; x1--; err += dx += b1; } /* x step */
+ } while (x0 <= x1);
+
+ while (y0-y1 < b) { /* too early stop of flat ellipses a=1 */
+ if (filled) {
+ drawHLine(x0-1, x0-1, y0, color, plotProc, data); /* -> finish tip of ellipse */
+ drawHLine(x1+1, x1+1, y0, color, plotProc, data);
+ drawHLine(x0-1, x0-1, y1, color, plotProc, data);
+ drawHLine(x1+1, x1+1, y1, color, plotProc, data);
+ } else {
+ (*plotProc)(x0-1, y0, color, data); /* -> finish tip of ellipse */
+ (*plotProc)(x1+1, y0, color, data);
+ (*plotProc)(x0-1, y1, color, data);
+ (*plotProc)(x1+1, y1, color, data);
+ }
+ y0++;
+ y1--;
+ }
+}
+
} // End of namespace Graphics
diff --git a/graphics/primitives.h b/graphics/primitives.h
index a3e8ab1565..62dc10bfdf 100644
--- a/graphics/primitives.h
+++ b/graphics/primitives.h
@@ -23,10 +23,21 @@
#ifndef GRAPHICS_PRIMITIVES_H
#define GRAPHICS_PRIMITIVES_H
+#include "common/rect.h"
+
namespace Graphics {
void drawLine(int x0, int y0, int x1, int y1, int color, void (*plotProc)(int, int, int, void *), void *data);
+void drawHLine(int x1, int x2, int y, int color, void (*plotProc)(int, int, int, void *), void *data);
+void drawVLine(int x, int y1, int y2, int color, void (*plotProc)(int, int, int, void *), void *data);
void drawThickLine(int x0, int y0, int x1, int y1, int penX, int penY, int color, void (*plotProc)(int, int, int, void *), void *data);
+void drawThickLine2(int x1, int y1, int x2, int y2, int thick, int color,
+ void (*plotProc)(int, int, int, void *), void *data);
+void drawFilledRect(Common::Rect &rect, int color, void (*plotProc)(int, int, int, void *), void *data);
+void drawRoundRect(Common::Rect &rect, int arc, int color, bool filled, void (*plotProc)(int, int, int, void *), void *data);
+void drawPolygonScan(int *polyX, int *polyY, int npoints, Common::Rect &bbox, int color,
+ void (*plotProc)(int, int, int, void *), void *data);
+void drawEllipse(int x0, int y0, int x1, int y1, int color, bool filled, void (*plotProc)(int, int, int, void *), void *data);
} // End of namespace Graphics