Initial commit
This commit is contained in:
298
engines/saga2/enchant.cpp
Normal file
298
engines/saga2/enchant.cpp
Normal file
@@ -0,0 +1,298 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* aint32 with this program; if not, write to the Free Software
|
||||
*
|
||||
*
|
||||
* Based on the original sources
|
||||
* Faery Tale II -- The Halls of the Dead
|
||||
* (c) 1993-1996 The Wyrmkeep Entertainment Co.
|
||||
*/
|
||||
|
||||
#include "saga2/saga2.h"
|
||||
#include "saga2/cmisc.h"
|
||||
#include "saga2/player.h"
|
||||
#include "saga2/enchant.h"
|
||||
|
||||
namespace Saga2 {
|
||||
|
||||
extern int16 objectProtoCount; // object prototype count
|
||||
|
||||
int enchantmentProto = -1;
|
||||
|
||||
void setEnchantmentDisplay();
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Enchantment Creation Function
|
||||
|
||||
ObjectID EnchantObject(
|
||||
ObjectID target,
|
||||
int enchantmentType,
|
||||
int duration) {
|
||||
GameObject *obj = GameObject::objectAddress(target);
|
||||
GameObject *ench;
|
||||
ProtoObj *enchProto;
|
||||
TilePoint slot;
|
||||
|
||||
assert(enchantmentProto >= 0);
|
||||
assert(enchantmentProto < objectProtoCount);
|
||||
|
||||
enchProto = g_vm->_objectProtos[enchantmentProto];
|
||||
|
||||
ench = GameObject::newObject(); //Create Enchantment
|
||||
if (ench == nullptr) return Nothing;
|
||||
|
||||
// Fill in the enchantment object. Note that the 'hitpoints'
|
||||
// of an enchantment are actually the duration of it's life
|
||||
// (in 10-second background cycles).
|
||||
//
|
||||
// Also note the use of the 'enchantment type' field to
|
||||
// indicate the effects of the enchantment. This is to
|
||||
// avoid having to create 50 new classes, representing
|
||||
// 50 new enchantments.
|
||||
ench->setScript(0);
|
||||
ench->setFlags(0, (uint8) - 1);
|
||||
ench->setHitPoints(duration);
|
||||
ench->setExtra(enchantmentType);
|
||||
ench->setProtoNum(enchantmentProto);
|
||||
|
||||
// Put in object's container
|
||||
if (obj->getAvailableSlot(ench, &slot))
|
||||
ench->move(Location(slot, target));
|
||||
|
||||
// Now, change the object base on enchantments
|
||||
obj->evalEnchantments();
|
||||
assert(enchProto->containmentSet() & ProtoObj::kIsEnchantment);
|
||||
assert((ench->protoAddress(ench->thisID()))->containmentSet() & ProtoObj::kIsEnchantment);
|
||||
return ench->thisID();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Function to deliberately dispel an enchantment
|
||||
|
||||
bool DispelObjectEnchantment(
|
||||
ObjectID target,
|
||||
int enchantmentType) {
|
||||
ObjectID enchID;
|
||||
|
||||
enchID = FindObjectEnchantment(target, enchantmentType);
|
||||
|
||||
if (enchID != Nothing) {
|
||||
GameObject *ench = GameObject::objectAddress(enchID);
|
||||
GameObject *obj = GameObject::objectAddress(target);
|
||||
|
||||
// Remove the enchantment and it's effects
|
||||
ench->deleteObject();
|
||||
obj->evalEnchantments();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Function to locate an enchantment on an object
|
||||
|
||||
ObjectID FindObjectEnchantment(
|
||||
ObjectID target,
|
||||
int enchantmentType) {
|
||||
GameObject *obj = GameObject::objectAddress(target);
|
||||
GameObject *containedObj;
|
||||
ObjectID objID;
|
||||
ContainerIterator iter(obj);
|
||||
|
||||
while ((objID = iter.next(&containedObj)) != Nothing) {
|
||||
ProtoObj *proto = containedObj->proto();
|
||||
|
||||
if ((proto->containmentSet() & ProtoObj::kIsEnchantment)
|
||||
&& ((containedObj->getExtra() & 0xFF00) == (enchantmentType & 0xFF00))) {
|
||||
return objID;
|
||||
}
|
||||
}
|
||||
|
||||
return Nothing;
|
||||
}
|
||||
|
||||
|
||||
void clearEnchantments(Actor *a) {
|
||||
ActorAttributes *ea = a->getStats();
|
||||
ActorAttributes *ba = a->getBaseStats();
|
||||
|
||||
ea->archery = ba->archery;
|
||||
ea->swordcraft = ba->swordcraft;
|
||||
ea->shieldcraft = ba->shieldcraft;
|
||||
ea->bludgeon = ba->bludgeon;
|
||||
ea->throwing = ba->throwing;
|
||||
ea->spellcraft = ba->spellcraft;
|
||||
ea->stealth = ba->stealth;
|
||||
ea->agility = ba->agility;
|
||||
ea->brawn = ba->brawn;
|
||||
ea->lockpick = ba->lockpick;
|
||||
ea->pilfer = ba->pilfer;
|
||||
ea->firstAid = ba->firstAid;
|
||||
ea->spotHidden = ba->spotHidden;
|
||||
|
||||
a->_enchantmentFlags = a->getBaseEnchantmentEffects();
|
||||
a->_effectiveResistance = a->getBaseResistance();
|
||||
a->_effectiveImmunity = a->getBaseImmunity();
|
||||
a->_recPointsPerUpdate = a->getBaseRecovery();
|
||||
}
|
||||
|
||||
void addEnchantment(Actor *a, uint16 enchantmentID) {
|
||||
ActorAttributes *ea = a->getStats();
|
||||
uint8 *stats = &ea->archery;
|
||||
uint16 eType = getEnchantmentType(enchantmentID);
|
||||
uint16 eSubType = getEnchantmentSubType(enchantmentID);
|
||||
int16 eAmount = getEnchantmentAmount(enchantmentID);
|
||||
|
||||
switch (eType) {
|
||||
case kEffectAttrib:
|
||||
stats[eSubType] = clamp(0, stats[eSubType] + eAmount, 100);
|
||||
break;
|
||||
case kEffectResist:
|
||||
a->setResist((effectResistTypes) eSubType, eAmount);
|
||||
break;
|
||||
case kEffectImmune:
|
||||
a->setImmune((effectImmuneTypes) eSubType, eAmount);
|
||||
break;
|
||||
case kEffectOthers:
|
||||
a->setEffect((effectOthersTypes) eSubType, eAmount);
|
||||
break;
|
||||
case kEffectSpecial: // damage shouldn't be an enchantment
|
||||
// Special code needed
|
||||
case kEffectDamage: // damage shouldn't be an enchantment
|
||||
case kEffectNone:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Function to eval the enchantments on an actor
|
||||
|
||||
void evalActorEnchantments(Actor *a) {
|
||||
GameObject *obj = nullptr;
|
||||
ObjectID id;
|
||||
PlayerActorID playerID;
|
||||
EnchantmentIterator iter(a);
|
||||
ContainerIterator cIter(a);
|
||||
|
||||
clearEnchantments(a);
|
||||
|
||||
for (id = iter.first(&obj); id != Nothing; id = iter.next(&obj)) {
|
||||
ProtoObj *proto = obj->proto();
|
||||
|
||||
if (proto->containmentSet() & ProtoObj::kIsEnchantment) {
|
||||
uint16 enchantmentID = obj->getExtra();
|
||||
addEnchantment(a, enchantmentID);
|
||||
}
|
||||
}
|
||||
|
||||
while (cIter.next(&obj)) {
|
||||
ProtoObj *proto = obj->proto();
|
||||
uint16 cSet = proto->containmentSet();
|
||||
|
||||
if ((cSet & (ProtoObj::kIsArmor | ProtoObj::kIsWeapon | ProtoObj::kIsWearable))
|
||||
&& proto->isObjectBeingUsed(obj)) {
|
||||
a->_effectiveResistance |= proto->resistance;
|
||||
a->_effectiveImmunity |= proto->immunity;
|
||||
}
|
||||
}
|
||||
|
||||
if (actorToPlayerID(a, playerID))
|
||||
recalcPortraitType(playerID);
|
||||
|
||||
if (a->thisID() == getCenterActorID())
|
||||
setEnchantmentDisplay();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Function to eval the enchantments on an actor
|
||||
|
||||
void evalObjectEnchantments(GameObject *obj) {
|
||||
// The only enchantment that currently works
|
||||
// on objects is the invisibility bit.
|
||||
//
|
||||
// If more enchantment types are added, then we'll
|
||||
// have to do this a bit differently...
|
||||
|
||||
if (FindObjectEnchantment(obj->thisID(), makeEnchantmentID(kEffectNonActor, kObjectInvisible, true)))
|
||||
obj->setFlags((uint8) - 1, kObjectInvisible);
|
||||
else
|
||||
obj->setFlags(0, kObjectInvisible);
|
||||
if (FindObjectEnchantment(obj->thisID(), makeEnchantmentID(kEffectNonActor, kObjectLocked, false)))
|
||||
obj->setFlags((uint8) - 1, kObjectLocked);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Enchantment iterator class
|
||||
|
||||
EnchantmentIterator::EnchantmentIterator(GameObject *container) {
|
||||
// Get the ID of the 1st object in the sector list
|
||||
_baseObject = container;
|
||||
_wornObject = nullptr;
|
||||
_nextID = Nothing;
|
||||
}
|
||||
|
||||
ObjectID EnchantmentIterator::first(GameObject **obj) {
|
||||
_nextID = _baseObject->IDChild();
|
||||
|
||||
return next(obj);
|
||||
}
|
||||
|
||||
ObjectID EnchantmentIterator::next(GameObject **obj) {
|
||||
GameObject *object;
|
||||
ObjectID id;
|
||||
|
||||
for (;;) {
|
||||
id = _nextID;
|
||||
|
||||
if (id == Nothing) {
|
||||
// If we were searching a 'worn' object, then pop up a level
|
||||
if (_wornObject) {
|
||||
_nextID = _wornObject->IDNext();
|
||||
_wornObject = nullptr;
|
||||
continue;
|
||||
}
|
||||
|
||||
return Nothing;
|
||||
}
|
||||
|
||||
// Get address of next object
|
||||
object = GameObject::objectAddress(id);
|
||||
|
||||
ProtoObj *proto = object->proto();
|
||||
uint16 cSet = proto->containmentSet();
|
||||
|
||||
if ((cSet & (ProtoObj::kIsArmor | ProtoObj::kIsWeapon | ProtoObj::kIsWearable))
|
||||
&& _wornObject == nullptr
|
||||
&& proto->isObjectBeingUsed(object)) {
|
||||
_wornObject = object;
|
||||
_nextID = object->IDChild();
|
||||
continue;
|
||||
}
|
||||
|
||||
_nextID = object->IDNext();
|
||||
|
||||
if (cSet & ProtoObj::kIsEnchantment) break;
|
||||
}
|
||||
|
||||
if (obj) *obj = object;
|
||||
return id;
|
||||
}
|
||||
|
||||
} // end of namespace Saga2
|
||||
Reference in New Issue
Block a user