Initial commit

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

View File

@@ -0,0 +1,55 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/ac/audio_clip_type.h"
#include "ags/shared/util/stream.h"
namespace AGS3 {
using AGS::Shared::Stream;
void AudioClipType::ReadFromFile(Stream *in) {
id = in->ReadInt32();
reservedChannels = in->ReadInt32();
volume_reduction_while_speech_playing = in->ReadInt32();
crossfadeSpeed = in->ReadInt32();
reservedForFuture = in->ReadInt32();
}
void AudioClipType::WriteToFile(Stream *out) {
out->WriteInt32(id);
out->WriteInt32(reservedChannels);
out->WriteInt32(volume_reduction_while_speech_playing);
out->WriteInt32(crossfadeSpeed);
out->WriteInt32(reservedForFuture);
}
void AudioClipType::ReadFromSavegame(Shared::Stream *in) {
volume_reduction_while_speech_playing = in->ReadInt32();
crossfadeSpeed = in->ReadInt32();
}
void AudioClipType::WriteToSavegame(Shared::Stream *out) const {
out->WriteInt32(volume_reduction_while_speech_playing);
out->WriteInt32(crossfadeSpeed);
}
} // namespace AGS3

View File

@@ -0,0 +1,52 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_AC_AUDIO_CLIP_TYPE_H
#define AGS_SHARED_AC_AUDIO_CLIP_TYPE_H
namespace AGS3 {
// Forward declaration
namespace AGS {
namespace Shared {
class Stream;
} // namespace Shared
} // namespace AGS
using namespace AGS; // FIXME later
#define AUDIO_CLIP_TYPE_SOUND 1
struct AudioClipType {
int id;
int reservedChannels;
int volume_reduction_while_speech_playing;
int crossfadeSpeed;
int reservedForFuture;
void ReadFromFile(Shared::Stream *in);
void WriteToFile(Shared::Stream *out);
void ReadFromSavegame(Shared::Stream *in);
void WriteToSavegame(Shared::Stream *out) const;
};
} // namespace AGS3
#endif

View File

@@ -0,0 +1,214 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/str.h"
#include "ags/shared/ac/character_info.h"
#include "ags/shared/ac/game_version.h"
#include "ags/shared/util/stream.h"
#include "ags/shared/util/string_utils.h"
#include "ags/globals.h"
namespace AGS3 {
using namespace AGS::Shared;
void CharacterInfo::ReadBaseFields(Stream *in) {
defview = in->ReadInt32();
talkview = in->ReadInt32();
view = in->ReadInt32();
room = in->ReadInt32();
prevroom = in->ReadInt32();
x = in->ReadInt32();
y = in->ReadInt32();
wait = in->ReadInt32();
flags = in->ReadInt32();
following = in->ReadInt16();
followinfo = in->ReadInt16();
idleview = in->ReadInt32();
idletime = in->ReadInt16();
idleleft = in->ReadInt16();
transparency = in->ReadInt16();
baseline = in->ReadInt16();
activeinv = in->ReadInt32();
talkcolor = in->ReadInt32();
thinkview = in->ReadInt32();
blinkview = in->ReadInt16();
blinkinterval = in->ReadInt16();
blinktimer = in->ReadInt16();
blinkframe = in->ReadInt16();
walkspeed_y = in->ReadInt16();
pic_yoffs = in->ReadInt16();
z = in->ReadInt32();
walkwait = in->ReadInt32();
speech_anim_speed = in->ReadInt16();
idle_anim_speed = in->ReadInt16();
blocking_width = in->ReadInt16();
blocking_height = in->ReadInt16();
index_id = in->ReadInt32();
pic_xoffs = in->ReadInt16();
walkwaitcounter = in->ReadInt16();
loop = in->ReadInt16();
frame = in->ReadInt16();
walking = in->ReadInt16();
animating = in->ReadInt16();
walkspeed = in->ReadInt16();
animspeed = in->ReadInt16();
in->ReadArrayOfInt16(inv, MAX_INV);
actx = in->ReadInt16();
acty = in->ReadInt16();
}
void CharacterInfo::WriteBaseFields(Stream *out) const {
out->WriteInt32(defview);
out->WriteInt32(talkview);
out->WriteInt32(view);
out->WriteInt32(room);
out->WriteInt32(prevroom);
out->WriteInt32(x);
out->WriteInt32(y);
out->WriteInt32(wait);
out->WriteInt32(flags);
out->WriteInt16(following);
out->WriteInt16(followinfo);
out->WriteInt32(idleview);
out->WriteInt16(idletime);
out->WriteInt16(idleleft);
out->WriteInt16(transparency);
out->WriteInt16(baseline);
out->WriteInt32(activeinv);
out->WriteInt32(talkcolor);
out->WriteInt32(thinkview);
out->WriteInt16(blinkview);
out->WriteInt16(blinkinterval);
out->WriteInt16(blinktimer);
out->WriteInt16(blinkframe);
out->WriteInt16(walkspeed_y);
out->WriteInt16(pic_yoffs);
out->WriteInt32(z);
out->WriteInt32(walkwait);
out->WriteInt16(speech_anim_speed);
out->WriteInt16(idle_anim_speed);
out->WriteInt16(blocking_width);
out->WriteInt16(blocking_height);
out->WriteInt32(index_id);
out->WriteInt16(pic_xoffs);
out->WriteInt16(walkwaitcounter);
out->WriteInt16(loop);
out->WriteInt16(frame);
out->WriteInt16(walking);
out->WriteInt16(animating);
out->WriteInt16(walkspeed);
out->WriteInt16(animspeed);
out->WriteArrayOfInt16(inv, MAX_INV);
out->WriteInt16(actx);
out->WriteInt16(acty);
}
void CharacterInfo::ReadFromFile(Stream *in, CharacterInfo2 &chinfo2, GameDataVersion data_ver) {
ReadBaseFields(in);
StrUtil::ReadCStrCount(name, in, LEGACY_MAX_CHAR_NAME_LEN);
StrUtil::ReadCStrCount(scrname, in, LEGACY_MAX_SCRIPT_NAME_LEN);
on = in->ReadInt8();
in->ReadInt8(); // alignment padding to int32
// Upgrade data
if (data_ver < kGameVersion_360_16) {
idle_anim_speed = animspeed + 5;
}
// Assign unrestricted names from legacy fields
chinfo2.name_new = name;
chinfo2.scrname_new = scrname;
}
void CharacterInfo::WriteToFile(Stream *out) const {
WriteBaseFields(out);
out->Write(name, LEGACY_MAX_CHAR_NAME_LEN);
out->Write(scrname, LEGACY_MAX_SCRIPT_NAME_LEN);
out->WriteInt8(on);
out->WriteInt8(0); // alignment padding to int32
}
void CharacterInfo::ReadFromSavegame(Stream *in, CharacterInfo2 &chinfo2, CharacterSvgVersion save_ver) {
ReadBaseFields(in);
if (save_ver < kCharSvgVersion_36115) { // Fixed-size name and scriptname
chinfo2.name_new.ReadCount(in, LEGACY_MAX_CHAR_NAME_LEN);
in->Seek(LEGACY_MAX_SCRIPT_NAME_LEN); // skip legacy scriptname
// (don't overwrite static data from save!)
} else {
chinfo2.name_new = StrUtil::ReadString(in);
}
on = in->ReadInt8();
//
// Upgrade restored data
if (save_ver < kCharSvgVersion_36025) {
idle_anim_speed = animspeed + 5;
}
// Fill legacy name fields, for compatibility with old scripts and plugins
snprintf(name, LEGACY_MAX_CHAR_NAME_LEN, "%s", chinfo2.name_new.GetCStr());
}
void CharacterInfo::WriteToSavegame(Stream *out, const CharacterInfo2 &chinfo2) const {
WriteBaseFields(out);
StrUtil::WriteString(chinfo2.name_new, out); // kCharSvgVersion_36115
out->WriteInt8(on);
}
#if defined (OBSOLETE)
#define COPY_CHAR_VAR(name) ci->name = oci->name
void ConvertOldCharacterToNew(OldCharacterInfo *oci, CharacterInfo *ci) {
COPY_CHAR_VAR(defview);
COPY_CHAR_VAR(talkview);
COPY_CHAR_VAR(view);
COPY_CHAR_VAR(room);
COPY_CHAR_VAR(prevroom);
COPY_CHAR_VAR(x);
COPY_CHAR_VAR(y);
COPY_CHAR_VAR(wait);
COPY_CHAR_VAR(flags);
COPY_CHAR_VAR(following);
COPY_CHAR_VAR(followinfo);
COPY_CHAR_VAR(idleview);
COPY_CHAR_VAR(idletime);
COPY_CHAR_VAR(idleleft);
COPY_CHAR_VAR(transparency);
COPY_CHAR_VAR(baseline);
COPY_CHAR_VAR(activeinv);
COPY_CHAR_VAR(loop);
COPY_CHAR_VAR(frame);
COPY_CHAR_VAR(walking);
COPY_CHAR_VAR(animating);
COPY_CHAR_VAR(walkspeed);
COPY_CHAR_VAR(animspeed);
COPY_CHAR_VAR(actx);
COPY_CHAR_VAR(acty);
COPY_CHAR_VAR(on);
snprintf(ci->name, sizeof(CharacterInfo::name), "%s", oci->name);
snprintf(ci->scrname, sizeof(CharacterInfo::scrname), "%s", oci->scrname);
memcpy(&ci->inv[0], &oci->inv[0], sizeof(short) * 100);
// move the talking colour into the struct and remove from flags
ci->talkcolor = (oci->flags & OCHF_SPEECHCOL) >> OCHF_SPEECHCOLSHIFT;
ci->flags = ci->flags & (~OCHF_SPEECHCOL);
}
#endif // OBSOLETE
} // namespace AGS3

View File

@@ -0,0 +1,263 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_AC_CHARACTER_INFO_H
#define AGS_SHARED_AC_CHARACTER_INFO_H
#include "common/std/vector.h"
#include "ags/shared/ac/common_defines.h" // constants
#include "ags/shared/ac/game_version.h"
#include "ags/shared/core/types.h"
#include "ags/shared/util/bbop.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class Stream;
} // namespace Shared
} // namespace AGS
using namespace AGS; // FIXME later
#define MAX_INV 301
// Character flags
#define CHF_MANUALSCALING 1
#define CHF_FIXVIEW 2 // between SetCharView and ReleaseCharView
#define CHF_NOINTERACT 4
#define CHF_NODIAGONAL 8
#define CHF_ALWAYSIDLE 0x10
#define CHF_NOLIGHTING 0x20
#define CHF_NOTURNING 0x40
#define CHF_NOWALKBEHINDS 0x80
#define CHF_FLIPSPRITE 0x100 // ?? Is this used??
#define CHF_NOBLOCKING 0x200
#define CHF_SCALEMOVESPEED 0x400
#define CHF_NOBLINKANDTHINK 0x800
#define CHF_SCALEVOLUME 0x1000
#define CHF_HASTINT 0x2000 // engine only
#define CHF_BEHINDSHEPHERD 0x4000 // engine only
#define CHF_AWAITINGMOVE 0x8000 // engine only
#define CHF_MOVENOTWALK 0x10000 // engine only - do not do walk anim
#define CHF_ANTIGLIDE 0x20000
#define CHF_HASLIGHT 0x40000
#define CHF_TINTLIGHTMASK (CHF_NOLIGHTING | CHF_HASTINT | CHF_HASLIGHT)
// Speechcol is no longer part of the flags as of v2.5
#define OCHF_SPEECHCOL 0xff000000
#define OCHF_SPEECHCOLSHIFT 24
#define UNIFORM_WALK_SPEED 0
#define FOLLOW_ALWAYSONTOP 0x7ffe
// Character's internal flags, packed in CharacterInfo::animating
#define CHANIM_MASK 0xFF
#define CHANIM_ON 0x01
#define CHANIM_REPEAT 0x02
#define CHANIM_BACKWARDS 0x04
// Converts character flags (CHF_*) to matching RoomObject flags (OBJF_*)
inline int CharFlagsToObjFlags(int chflags) {
using namespace AGS::Shared;
return FlagToFlag(chflags, CHF_NOINTERACT, OBJF_NOINTERACT) |
FlagToFlag(chflags, CHF_NOWALKBEHINDS, OBJF_NOWALKBEHINDS) |
FlagToFlag(chflags, CHF_HASTINT, OBJF_HASTINT) |
FlagToFlag(chflags, CHF_HASLIGHT, OBJF_HASLIGHT) |
// following flags are inverse
FlagToNoFlag(chflags, CHF_NOLIGHTING, OBJF_USEREGIONTINTS) |
FlagToNoFlag(chflags, CHF_MANUALSCALING, OBJF_USEROOMSCALING) |
FlagToNoFlag(chflags, CHF_NOBLOCKING, OBJF_SOLID);
}
// Length of deprecated character name field, in bytes
#define LEGACY_MAX_CHAR_NAME_LEN 40
enum CharacterSvgVersion {
kCharSvgVersion_Initial = 0, // [UNSUPPORTED] from 3.5.0 pre-alpha
kCharSvgVersion_350 = 1, // new movelist format (along with pathfinder)
kCharSvgVersion_36025 = 2, // animation volume
kCharSvgVersion_36109 = 3, // removed movelists, save externally
kCharSvgVersion_36115 = 4, // no limit on character name's length
};
// Predeclare a design-time Character extension
struct CharacterInfo2;
// Predeclare a runtime Character extension (TODO: refactor and remove this from here)
struct CharacterExtras;
// CharacterInfo is a design-time Character data.
// Contains original set of character fields.
// IMPORTANT: exposed to script API, and plugin API as AGSCharacter!
// For older script compatibility the struct also has to maintain its size,
// and be stored in a plain array to keep the relative memory address offsets
// between the Character objects!
// Do not add or change existing fields, unless planning breaking compatibility.
// Prefer to use CharacterInfo2 and CharacterExtras structs for any extensions.
//
// TODO: must refactor, some parts of it should be in a runtime Character class.
struct CharacterInfo {
int defview;
int talkview;
int view;
int room, prevroom;
int x, y, wait;
int flags;
short following;
short followinfo;
int idleview; // the loop will be randomly picked
short idletime, idleleft; // num seconds idle before playing anim
short transparency; // if character is transparent
short baseline;
int activeinv;
int talkcolor;
int thinkview;
short blinkview, blinkinterval; // design time
short blinktimer, blinkframe; // run time
short walkspeed_y;
short pic_yoffs; // this is fixed in screen coordinates
int z; // z-location, for flying etc
int walkwait;
short speech_anim_speed, idle_anim_speed;
short blocking_width, blocking_height;
int index_id; // used for object functions to know the id
short pic_xoffs; // this is fixed in screen coordinates
short walkwaitcounter;
uint16_t loop, frame;
short walking; // stores movelist index, optionally +TURNING_AROUND
short animating; // stores CHANIM_* flags in lower byte and delay in upper byte
short walkspeed, animspeed;
short inv[MAX_INV];
short actx, acty;
// These two name fields are deprecated, but must stay here
// for compatibility with old scripts and plugin API
char name[LEGACY_MAX_CHAR_NAME_LEN];
char scrname[LEGACY_MAX_SCRIPT_NAME_LEN];
int8 on;
int get_effective_y() const; // return Y - Z
int get_baseline() const; // return baseline, or Y if not set
int get_blocking_top() const; // return Y - BlockingHeight/2
int get_blocking_bottom() const; // return Y + BlockingHeight/2
// Returns effective x/y walkspeeds for this character
void get_effective_walkspeeds(int &walk_speed_x, int &walk_speed_y) const {
walk_speed_x = walkspeed;
walk_speed_y = ((walkspeed_y == UNIFORM_WALK_SPEED) ? walkspeed : walkspeed_y);
}
inline bool has_explicit_light() const {
return (flags & CHF_HASLIGHT) != 0;
}
inline bool has_explicit_tint() const {
return (flags & CHF_HASTINT) != 0;
}
inline bool is_animating() const {
return (animating & CHANIM_ON) != 0;
}
inline int get_anim_repeat() const {
return (animating & CHANIM_REPEAT) ? ANIM_REPEAT : ANIM_ONCE;
}
inline bool get_anim_forwards() const {
return (animating & CHANIM_BACKWARDS) == 0;
}
inline int get_anim_delay() const {
return (animating >> 8) & 0xFF;
}
inline void set_animating(bool repeat, bool forwards, int delay) {
animating = CHANIM_ON |
(CHANIM_REPEAT * repeat) |
(CHANIM_BACKWARDS * !forwards) |
((delay & 0xFF) << 8);
}
// [IKM] 2012-06-28: I still have to pass char_index to some of those functions
// either because they use it to set some variables with it,
// or because they pass it further to other functions, that are called from various places
// and it would be too much to change them all simultaneously
//
// [IKM] 2016-08-26: these methods should NOT be in CharacterInfo class,
// bit in distinct runtime character class!
void UpdateMoveAndAnim(int &char_index, CharacterExtras *chex, std::vector<int> &followingAsSheep);
void UpdateFollowingExactlyCharacter();
int update_character_walkturning(CharacterExtras *chex);
void update_character_moving(int &char_index, CharacterExtras *chex, int &doing_nothing);
int update_character_animating(int &char_index, int &doing_nothing);
void update_character_idle(CharacterExtras *chex, int &doing_nothing);
void update_character_follower(int &char_index, std::vector<int> &followingAsSheep, int &doing_nothing);
void ReadFromFile(Shared::Stream *in, CharacterInfo2 &chinfo2, GameDataVersion data_ver);
void WriteToFile(Shared::Stream *out) const;
// TODO: move to runtime-only class (?)
void ReadFromSavegame(Shared::Stream *in, CharacterInfo2 &chinfo2, CharacterSvgVersion save_ver);
void WriteToSavegame(Shared::Stream *out, const CharacterInfo2 &chinfo2) const;
private:
// Fixups loop and frame values, in case any of them are set to a value out of the valid range
void FixupCurrentLoopAndFrame();
// Helper functions that read and write first data fields,
// common for both game file and save.
void ReadBaseFields(Shared::Stream *in);
void WriteBaseFields(Shared::Stream *out) const;
};
// Design-time Character extended fields
struct CharacterInfo2 {
// Unrestricted scriptname and name fields
AGS::Shared::String scrname_new;
AGS::Shared::String name_new;
};
#if defined (OBSOLETE)
struct OldCharacterInfo {
int defview;
int talkview;
int view;
int room, prevroom;
int x, y, wait;
int flags;
short following;
short followinfo;
int idleview; // the loop will be randomly picked
short idletime, idleleft; // num seconds idle before playing anim
short transparency; // if character is transparent
short baseline;
int activeinv; // this is an INT to support SeeR (no signed shorts)
short loop, frame;
short walking, animating;
short walkspeed, animspeed;
short inv[100];
short actx, acty;
char name[30];
char scrname[16];
int8 on;
};
void ConvertOldCharacterToNew(OldCharacterInfo *oci, CharacterInfo *ci);
#endif // OBSOLETE
} // namespace AGS3
#endif

View File

@@ -0,0 +1,54 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/ac/common.h"
#include "ags/shared/util/string.h"
#include "ags/globals.h"
namespace AGS3 {
using namespace AGS::Shared;
void quit(const String &str) {
quit(str.GetCStr());
}
void quitprintf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
String text = String::FromFormatV(fmt, ap);
va_end(ap);
// WORKAROUND: In ScummVM we have to make this an error, because
// too many places calling it presume it doesn't return,
// and will throw a wobbly if does
error("%s", text.GetCStr());
}
void set_our_eip(int eip) {
_G(our_eip) = eip;
}
int get_our_eip() {
return _G(our_eip);
}
} // namespace AGS3

View File

@@ -0,0 +1,40 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_AC_COMMON_H
#define AGS_SHARED_AC_COMMON_H
#include "ags/shared/util/string.h"
namespace AGS3 {
// These are the project-dependent functions, they are defined both in Engine.App and AGS.Native.
void quit(const AGS::Shared::String &str);
void quit(const char *);
void quitprintf(const char *fmt, ...);
void set_our_eip(int eip);
int get_our_eip();
extern const char *game_file_sig;
} // namespace AGS3
#endif

View File

@@ -0,0 +1,131 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_AC_COMMON_DEFINES_H
#define AGS_SHARED_AC_COMMON_DEFINES_H
#include "ags/shared/core/platform.h"
namespace AGS3 {
// Some arbitrary return values, should be replaced with either
// simple boolean, or HError
#define EXIT_NORMAL 0
#define EXIT_CRASH 92
#define EXIT_ERROR 93
// Legacy (UNSUPPORTED!) interaction script constants
//
// NUMCONDIT : whataction[0]: Char walks off left
// [1]: Char walks off right
// [2]: Char walks off bottom
// [3]: Char walks off top
// [4]: First enters screen
// [5]: Every time enters screen
// [6]: execute every loop
// [5]...[19]: Char stands on lookat type
// [20]...[35]: Look at type
// [36]...[49]: Action on type
// [50]...[65]: Use inv on type
// [66]...[75]: Look at object
// [76]...[85]: Action on object
// [86]...[95]: Speak to object
// [96]...[105]: Use inv on object
// [106]...[124]: Misc conditions 1-20
// game ver whataction[]=
// v1.00 0 : Go to screen
// 1 : Don't do anything
// 2 : Can't walk
// 3 : Man dies
// 4 : Run animation
// 5 : Display message
// 6 : Remove an object (set object.on=0)
// 7 : Remove object & add Val2 to inventory
// 8 : Add Val1 to inventory (Val2=num times)
// 9 : Run a script
// v1.00 SR-1 10 : Run graphical script
// v1.1 11 : Play sound effect SOUND%d.WAV
// v1.12 12 : Play FLI/FLC animation FLIC%d.FLC or FLIC%d.FLI
// 13 : Turn object on
// v2.00 14 : Run conversation
#if defined(OBSOLETE)
#define NUM_MISC 20
#define NUMOTCON 7 // number of conditions before standing on
#define NUM_CONDIT (120 + NUMOTCON)
#define MISC_COND (MAX_WALK_BEHINDS * 4 + NUMOTCON + MAX_ROOM_OBJECTS * 4)
#define NUMRESPONSE 14
#define NUMCOMMANDS 15
#define GO_TO_SCREEN 0
#define NO_ACTION 1
#define NO_WALK 2
#define MAN_DIES 3
#define RUN_ANIMATE 4
#define SHOW_MESSAGE 5
#define OBJECT_OFF 6
#define OBJECT_INV 7
#define ADD_INV 8
#define RUNSCRIPT 9
#define GRAPHSCRIPT 10
#define PLAY_SOUND 11
#define PLAY_FLI 12
#define OBJECT_ON 13
#define RUN_DIALOG 14
#endif
// Script name length limit for some game objects
#define LEGACY_MAX_SCRIPT_NAME_LEN 20
// Number of state-saved rooms
#define MAX_ROOMS 300
// Some obsolete room data, likely pre-2.5
#define MAX_LEGACY_ROOM_FLAGS 15
// Old object name limit
#define LEGACY_MAXOBJNAMELEN 30
// Max number of sprites in older versions
#define LEGACY_MAX_SPRITES_V25 6000
#define LEGACY_MAX_SPRITES 30000
// The game to screen coordinate conversion multiplier, was used in older high-res games
#define HIRES_COORD_MULTIPLIER 2
// Room object flags (currently limited by a byte)
#define OBJF_NOINTERACT 0x01 // not clickable
#define OBJF_NOWALKBEHINDS 0x02 // ignore walk-behinds
#define OBJF_HASTINT 0x04 // the tint_* members are valid
#define OBJF_USEREGIONTINTS 0x08 // obey region tints/light areas
#define OBJF_USEROOMSCALING 0x10 // obey room scaling areas
#define OBJF_SOLID 0x20 // blocks characters from moving
#define OBJF_LEGACY_LOCKED 0x40 // object position is locked in the editor (OBSOLETE since 3.5.0)
#define OBJF_HASLIGHT 0x80 // the tint_light is valid and treated as brightness
#define OBJF_TINTLIGHTMASK (OBJF_HASTINT | OBJF_HASLIGHT | OBJF_USEREGIONTINTS)
// Animation flow mode
// NOTE: had to move to common_defines, because used by CharacterInfo
// Animates once and stops at the *last* frame
#define ANIM_ONCE 0
// Animates infinitely until stopped by command
#define ANIM_REPEAT 1
// Animates once and stops, resetting to the very first frame
#define ANIM_ONCERESET 2
} // namespace AGS3
#endif

View File

@@ -0,0 +1,48 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/ac/dialog_topic.h"
#include "ags/shared/util/stream.h"
namespace AGS3 {
using AGS::Shared::Stream;
void DialogTopic::ReadFromFile(Stream *in) {
in->ReadArray(optionnames, 150 * sizeof(char), MAXTOPICOPTIONS);
in->ReadArrayOfInt32(optionflags, MAXTOPICOPTIONS);
in->ReadInt32(); // optionscripts 32-bit pointer
in->ReadArrayOfInt16(entrypoints, MAXTOPICOPTIONS);
startupentrypoint = in->ReadInt16();
codesize = in->ReadInt16();
numoptions = in->ReadInt32();
topicFlags = in->ReadInt32();
}
void DialogTopic::ReadFromSavegame(Shared::Stream *in) {
in->ReadArrayOfInt32(optionflags, MAXTOPICOPTIONS);
}
void DialogTopic::WriteToSavegame(Shared::Stream *out) const {
out->WriteArrayOfInt32(optionflags, MAXTOPICOPTIONS);
}
} // namespace AGS3

View File

@@ -0,0 +1,86 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_AC_DIALOG_TOPIC_H
#define AGS_SHARED_AC_DIALOG_TOPIC_H
#include "ags/shared/core/types.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class Stream;
} // namespace Shared
} // namespace AGS
using namespace AGS; // FIXME later
// [IKM] This is *conversation* dialog, not *gui* dialog, mind you!
#define MAXTOPICOPTIONS 30
#define DFLG_ON 1 // currently enabled
#define DFLG_OFFPERM 2 // off forever (can't be trurned on)
#define DFLG_NOREPEAT 4 // character doesn't repeat it when clicked
#define DFLG_HASBEENCHOSEN 8 // dialog option is 'read'
#define DTFLG_SHOWPARSER 1 // show parser in this topic
#define DCMD_SAY 1
#define DCMD_OPTOFF 2
#define DCMD_OPTON 3
#define DCMD_RETURN 4
#define DCMD_STOPDIALOG 5
#define DCMD_OPTOFFFOREVER 6
#define DCMD_RUNTEXTSCRIPT 7
#define DCMD_GOTODIALOG 8
#define DCMD_PLAYSOUND 9
#define DCMD_ADDINV 10
#define DCMD_SETSPCHVIEW 11
#define DCMD_NEWROOM 12
#define DCMD_SETGLOBALINT 13
#define DCMD_GIVESCORE 14
#define DCMD_GOTOPREVIOUS 15
#define DCMD_LOSEINV 16
#define DCMD_ENDSCRIPT 0xff
#define DCHAR_NARRATOR 999
#define DCHAR_PLAYER 998
struct DialogTopic {
char optionnames[MAXTOPICOPTIONS][150];
int32_t optionflags[MAXTOPICOPTIONS];
short entrypoints[MAXTOPICOPTIONS];
short startupentrypoint;
short codesize;
int numoptions;
int topicFlags;
// NOTE: optionscripts is an unknown data from before AGS 2.5
#ifdef OBSOLETE
std::vector<uint8_t> optionscripts;
#endif
void ReadFromFile(Shared::Stream *in);
void ReadFromSavegame(Shared::Stream *in);
void WriteToSavegame(Shared::Stream *out) const;
};
} // namespace AGS3
#endif

View File

@@ -0,0 +1,44 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/ac/dynobj/script_audio_clip.h"
#include "ags/shared/util/stream.h"
namespace AGS3 {
using namespace AGS::Shared;
void ScriptAudioClip::ReadFromFile(Stream *in) {
id = in->ReadInt32();
scriptName.ReadCount(in, LEGACY_AUDIOCLIP_SCRIPTNAMELENGTH);
fileName.ReadCount(in, LEGACY_AUDIOCLIP_FILENAMELENGTH);
bundlingType = static_cast<uint8_t>(in->ReadInt8());
type = static_cast<uint8_t>(in->ReadInt8());
fileType = static_cast<AudioFileType>(in->ReadInt8());
defaultRepeat = in->ReadInt8();
in->ReadInt8(); // alignment padding to int16
defaultPriority = in->ReadInt16();
defaultVolume = in->ReadInt16();
in->ReadInt16(); // alignment padding to int32
in->ReadInt32(); // reserved
}
} // namespace AGS3

View File

@@ -0,0 +1,68 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_AC_DYNOBJ_SCRIPT_AUDIO_CLIP_H
#define AGS_SHARED_AC_DYNOBJ_SCRIPT_AUDIO_CLIP_H
#include "ags/shared/util/string.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class Stream;
} // namespace Shared
} // namespace AGS
using namespace AGS; // FIXME later
enum AudioFileType {
eAudioFileOGG = 1,
eAudioFileMP3 = 2,
eAudioFileWAV = 3,
eAudioFileVOC = 4,
eAudioFileMIDI = 5,
eAudioFileMOD = 6
};
#define AUCL_BUNDLE_EXE 1
#define AUCL_BUNDLE_VOX 2
#define LEGACY_AUDIOCLIP_SCRIPTNAMELENGTH 30
#define LEGACY_AUDIOCLIP_FILENAMELENGTH 15
struct ScriptAudioClip {
int id = 0;
Shared::String scriptName;
Shared::String fileName;
uint8_t bundlingType = AUCL_BUNDLE_EXE;
uint8_t type = 0;
AudioFileType fileType = eAudioFileOGG;
int8 defaultRepeat = 0;
short defaultPriority = 50;
short defaultVolume = 100;
void ReadFromFile(Shared::Stream *in);
};
} // namespace AGS3
#endif

View File

@@ -0,0 +1,422 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/ac/audio_clip_type.h"
#include "ags/shared/ac/game_setup_struct.h"
#include "ags/shared/ac/old_game_setup_struct.h"
#include "ags/shared/ac/words_dictionary.h"
#include "ags/shared/ac/dynobj/script_audio_clip.h"
#include "ags/shared/game/interactions.h"
#include "ags/shared/util/string_utils.h"
#include "ags/globals.h"
namespace AGS3 {
using namespace AGS::Shared;
GameSetupStruct::GameSetupStruct()
: filever(0)
, roomCount(0)
, scoreClipID(0) {
memset(lipSyncFrameLetters, 0, sizeof(lipSyncFrameLetters));
memset(guid, 0, sizeof(guid));
memset(saveGameFileExtension, 0, sizeof(saveGameFileExtension));
}
GameSetupStruct::~GameSetupStruct() {
Free();
}
void GameSetupStruct::Free() {
GameSetupStructBase::Free();
fonts.clear();
mcurs.clear();
intrChar.clear();
charScripts.clear();
charProps.clear();
// TODO: find out if it really needs to begin with 1 here?
for (size_t i = 1; i < (size_t)MAX_INV; i++) {
intrInv[i].reset();
invProps[i].clear();
}
invScripts.clear();
numinvitems = 0;
viewNames.clear();
dialogScriptNames.clear();
roomNames.clear();
roomNumbers.clear();
roomCount = 0;
audioClips.clear();
audioClipTypes.clear();
SpriteInfos.clear();
}
// Assigns font info parameters using legacy flags value read from the game data
void SetFontInfoFromLegacyFlags(FontInfo &finfo, const uint8_t data) {
finfo.Flags = (data >> 6) & 0xFF;
finfo.Size = data & FFLG_LEGACY_SIZEMASK;
}
void AdjustFontInfoUsingFlags(FontInfo &finfo, const uint32_t flags) {
finfo.Flags = flags;
if ((flags & FFLG_SIZEMULTIPLIER) != 0) {
finfo.SizeMultiplier = finfo.Size;
finfo.Size = 0;
}
}
ScriptAudioClip *GetAudioClipForOldStyleNumber(GameSetupStruct &game, bool is_music, int num) {
String clip_name;
if (is_music)
clip_name.Format("aMusic%d", num);
else
clip_name.Format("aSound%d", num);
for (size_t i = 0; i < _GP(game).audioClips.size(); ++i) {
if (clip_name.CompareNoCase(_GP(game).audioClips[i].scriptName) == 0)
return &_GP(game).audioClips[i];
}
return nullptr;
}
//-----------------------------------------------------------------------------
// Reading Part 1
void GameSetupStruct::read_savegame_info(Shared::Stream *in, GameDataVersion data_ver) {
if (data_ver > kGameVersion_272) { // only 3.x
StrUtil::ReadCStrCount(guid, in, MAX_GUID_LENGTH);
StrUtil::ReadCStrCount(saveGameFileExtension, in, MAX_SG_EXT_LENGTH);
saveGameFolderName.ReadCount(in, LEGACY_MAX_SG_FOLDER_LEN);
}
}
void GameSetupStruct::read_font_infos(Shared::Stream *in, GameDataVersion data_ver) {
fonts.resize(numfonts);
if (data_ver < kGameVersion_350) {
for (int i = 0; i < numfonts; ++i)
SetFontInfoFromLegacyFlags(fonts[i], in->ReadInt8());
for (int i = 0; i < numfonts; ++i)
fonts[i].Outline = in->ReadInt8(); // size of char
if (data_ver < kGameVersion_341)
return;
for (int i = 0; i < numfonts; ++i) {
fonts[i].YOffset = in->ReadInt32();
if (data_ver >= kGameVersion_341_2)
fonts[i].LineSpacing = MAX<int32_t>(0, in->ReadInt32());
}
} else {
for (int i = 0; i < numfonts; ++i) {
uint32_t flags = in->ReadInt32();
fonts[i].Size = in->ReadInt32();
fonts[i].Outline = in->ReadInt32();
fonts[i].YOffset = in->ReadInt32();
fonts[i].LineSpacing = MAX<int32_t>(0, in->ReadInt32());
AdjustFontInfoUsingFlags(fonts[i], flags);
}
}
}
void GameSetupStruct::ReadInvInfo(Stream *in) {
for (int i = 0; i < numinvitems; ++i) {
invinfo[i].ReadFromFile(in);
}
}
void GameSetupStruct::WriteInvInfo(Stream *out) {
for (int i = 0; i < numinvitems; ++i) {
invinfo[i].WriteToFile(out);
}
}
HGameFileError GameSetupStruct::read_cursors(Shared::Stream *in) {
mcurs.resize(numcursors);
ReadMouseCursors(in);
return HGameFileError::None();
}
void GameSetupStruct::read_interaction_scripts(Shared::Stream *in, GameDataVersion data_ver) {
_G(numGlobalVars) = 0;
if (data_ver > kGameVersion_272) { // 3.x
charScripts.resize(numcharacters);
invScripts.resize(numinvitems);
for (size_t i = 0; i < (size_t)numcharacters; ++i)
charScripts[i].reset(InteractionScripts::CreateFromStream(in));
// NOTE: new inventory items' events are loaded starting from 1 for some reason
for (size_t i = 1; i < (size_t)numinvitems; ++i)
invScripts[i].reset(InteractionScripts::CreateFromStream(in));
} else { // 2.x
intrChar.resize(numcharacters);
for (size_t i = 0; i < (size_t)numcharacters; ++i)
intrChar[i].reset(Interaction::CreateFromStream(in));
for (size_t i = 0; i < (size_t)numinvitems; ++i)
intrInv[i].reset(Interaction::CreateFromStream(in));
_G(numGlobalVars) = in->ReadInt32();
for (size_t i = 0; i < (size_t)_G(numGlobalVars); ++i)
_G(globalvars)[i].Read(in);
}
}
void GameSetupStruct::read_words_dictionary(Shared::Stream *in) {
dict.reset(new WordsDictionary());
read_dictionary(dict.get(), in);
}
void GameSetupStruct::ReadMouseCursors(Stream *in) {
for (int i = 0; i < numcursors; ++i) {
mcurs[i].ReadFromFile(in);
}
}
void GameSetupStruct::WriteMouseCursors(Stream *out) {
for (int i = 0; i < numcursors; ++i) {
mcurs[i].WriteToFile(out);
}
}
//-----------------------------------------------------------------------------
// Reading Part 2
void GameSetupStruct::read_characters(Shared::Stream *in) {
chars.resize(numcharacters);
chars2.resize(numcharacters);
ReadCharacters(in);
}
void GameSetupStruct::read_lipsync(Shared::Stream *in, GameDataVersion data_ver) {
if (data_ver >= kGameVersion_254) // lip syncing was introduced in 2.54
in->ReadArray(&lipSyncFrameLetters[0][0], MAXLIPSYNCFRAMES, 50);
}
void GameSetupStruct::read_messages(Shared::Stream *in, const std::array<int32_t> &load_messages, GameDataVersion data_ver) {
char mbuf[GLOBALMESLENGTH];
for (int i = 0; i < MAXGLOBALMES; ++i) {
if (!load_messages[i])
continue;
if (data_ver < kGameVersion_261) { // Global messages are not encrypted on < 2.61
char *nextchar = mbuf;
// TODO: probably this is same as fgetstring
while (1) {
*nextchar = in->ReadInt8();
if (*nextchar == 0)
break;
nextchar++;
}
} else {
read_string_decrypt(in, mbuf, GLOBALMESLENGTH);
}
messages[i] = mbuf;
}
}
void GameSetupStruct::ReadCharacters(Stream *in) {
for (int i = 0; i < numcharacters; ++i) {
chars[i].ReadFromFile(in, chars2[i], _G(loaded_game_file_version));
}
}
void GameSetupStruct::WriteCharacters(Stream *out) {
for (int i = 0; i < numcharacters; ++i) {
chars[i].WriteToFile(out);
}
}
//-----------------------------------------------------------------------------
// Reading Part 3
HGameFileError GameSetupStruct::read_customprops(Shared::Stream *in, GameDataVersion data_ver) {
dialogScriptNames.resize(numdialog);
viewNames.resize(numviews);
if (data_ver >= kGameVersion_260) { // >= 2.60
if (Properties::ReadSchema(propSchema, in) != kPropertyErr_NoError)
return new MainGameFileError(kMGFErr_InvalidPropertySchema);
int errors = 0;
charProps.resize(numcharacters);
for (int i = 0; i < numcharacters; ++i) {
errors += Properties::ReadValues(charProps[i], in);
}
for (int i = 0; i < numinvitems; ++i) {
errors += Properties::ReadValues(invProps[i], in);
}
if (errors > 0)
return new MainGameFileError(kMGFErr_InvalidPropertyValues);
for (int i = 0; i < numviews; ++i)
viewNames[i] = String::FromStream(in);
if (data_ver >= kGameVersion_270) {
for (int i = 0; i < numinvitems; ++i)
invScriptNames[i] = String::FromStream(in);
if (data_ver >= kGameVersion_272) {
for (int i = 0; i < numdialog; ++i)
dialogScriptNames[i] = String::FromStream(in);
}
}
}
return HGameFileError::None();
}
HGameFileError GameSetupStruct::read_audio(Shared::Stream *in, GameDataVersion data_ver) {
if (data_ver >= kGameVersion_320) {
size_t audiotype_count = in->ReadInt32();
audioClipTypes.resize(audiotype_count);
for (size_t i = 0; i < audiotype_count; ++i) {
audioClipTypes[i].ReadFromFile(in);
}
size_t audioclip_count = in->ReadInt32();
audioClips.resize(audioclip_count);
ReadAudioClips(in, audioclip_count);
scoreClipID = in->ReadInt32();
}
return HGameFileError::None();
}
// Temporarily copied this from acruntim.h;
// it is unknown if this should be defined for all solution, or only runtime
#define STD_BUFFER_SIZE 3000
void GameSetupStruct::read_room_names(Stream *in, GameDataVersion data_ver) {
if ((data_ver >= kGameVersion_301) && (options[OPT_DEBUGMODE] != 0)) {
roomCount = in->ReadInt32();
roomNumbers.resize(roomCount);
roomNames.resize(roomCount);
for (int i = 0; i < roomCount; ++i) {
roomNumbers[i] = in->ReadInt32();
roomNames[i].Read(in);
}
} else {
roomCount = 0;
}
}
void GameSetupStruct::ReadAudioClips(Shared::Stream *in, size_t count) {
for (size_t i = 0; i < count; ++i) {
audioClips[i].ReadFromFile(in);
}
}
void GameSetupStruct::ReadFromSaveGame_v321(Stream *in) {
// NOTE: the individual object data is read from legacy saves
// same way as if it were from a game file
ReadInvInfo(in);
ReadMouseCursors(in);
if (_G(loaded_game_file_version) <= kGameVersion_272) {
for (int i = 0; i < numinvitems; ++i)
intrInv[i]->ReadTimesRunFromSave_v321(in);
for (int i = 0; i < numcharacters; ++i)
intrChar[i]->ReadTimesRunFromSave_v321(in);
}
in->ReadArrayOfInt32(&options[0], OPT_HIGHESTOPTION_321 + 1);
options[OPT_LIPSYNCTEXT] = in->ReadInt8();
ReadCharacters(in);
}
//=============================================================================
#if defined (OBSOLETE)
void ConvertOldGameStruct(OldGameSetupStruct *ogss, GameSetupStruct *gss) {
snprintf(gss->gamename, sizeof(GameSetupStruct::gamename), "%s", ogss->gamename);
for (int i = 0; i < 20; i++)
gss->options[i] = ogss->options[i];
memcpy(&gss->paluses[0], &ogss->paluses[0], 256);
memcpy(&gss->defpal[0], &ogss->defpal[0], 256 * sizeof(RGB));
gss->numviews = ogss->numviews;
gss->numcharacters = ogss->numcharacters;
gss->playercharacter = ogss->playercharacter;
gss->totalscore = ogss->totalscore;
gss->numinvitems = ogss->numinvitems;
gss->numdialog = ogss->numdialog;
gss->numdlgmessage = ogss->numdlgmessage;
gss->numfonts = ogss->numfonts;
gss->color_depth = ogss->color_depth;
gss->target_win = ogss->target_win;
gss->dialog_bullet = ogss->dialog_bullet;
gss->hotdot = ogss->hotdot;
gss->hotdotouter = ogss->hotdotouter;
gss->uniqueid = ogss->uniqueid;
gss->numgui = ogss->numgui;
for (int i = 0; i < 10; ++i) {
SetFontInfoFromLegacyFlags(gss->fonts[i], ogss->fontflags[i]);
gss->fonts[i].Outline = ogss->fontoutline[i];
}
for (int i = 0; i < LEGACY_MAX_SPRITES_V25; ++i) {
gss->SpriteInfos[i].Flags = ogss->spriteflags[i];
}
memcpy(&gss->invinfo[0], &ogss->invinfo[0], 100 * sizeof(InventoryItemInfo));
for (int i = 0; i < 10; ++i)
gss->mcurs[i] = ogss->mcurs[i];
for (int i = 0; i < MAXGLOBALMES; i++)
gss->messages[i] = ogss->messages[i];
gss->dict = ogss->dict;
gss->globalscript = ogss->globalscript;
gss->chars = nullptr; //ogss->chars;
gss->compiled_script = ogss->compiled_script;
gss->numcursors = 10;
}
#endif // OBSOLETE
void GameSetupStruct::ReadFromSavegame(Stream *in) {
// of GameSetupStruct
in->ReadArrayOfInt32(options, OPT_HIGHESTOPTION_321 + 1);
options[OPT_LIPSYNCTEXT] = in->ReadInt32();
// of GameSetupStructBase
playercharacter = in->ReadInt32();
dialog_bullet = in->ReadInt32();
hotdot = static_cast<uint16_t>(in->ReadInt16());
hotdotouter = static_cast<uint16_t>(in->ReadInt16());
invhotdotsprite = in->ReadInt32();
default_lipsync_frame = in->ReadInt32();
}
void GameSetupStruct::WriteForSavegame(Stream *out) {
// of GameSetupStruct
out->WriteArrayOfInt32(options, OPT_HIGHESTOPTION_321 + 1);
out->WriteInt32(options[OPT_LIPSYNCTEXT]);
// of GameSetupStructBase
out->WriteInt32(playercharacter);
out->WriteInt32(dialog_bullet);
out->WriteInt16(static_cast<uint16_t>(hotdot));
out->WriteInt16(static_cast<uint16_t>(hotdotouter));
out->WriteInt32(invhotdotsprite);
out->WriteInt32(default_lipsync_frame);
}
} // namespace AGS3

View File

@@ -0,0 +1,189 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// GameSetupStruct is a contemporary main game data.
//
//=============================================================================
#ifndef AGS_SHARED_AC_GAME_SETUP_STRUCT_H
#define AGS_SHARED_AC_GAME_SETUP_STRUCT_H
#include "common/std/array.h"
#include "common/std/vector.h"
#include "ags/shared/ac/audio_clip_type.h"
#include "ags/shared/ac/character_info.h" // TODO: constants to separate header
#include "ags/shared/ac/game_setup_struct_base.h"
#include "ags/shared/ac/inventory_item_info.h"
#include "ags/shared/ac/mouse_cursor.h"
#include "ags/shared/ac/dynobj/script_audio_clip.h"
#include "ags/shared/game/custom_properties.h"
#include "ags/shared/game/main_game_file.h" // TODO: constants to separate header or split out reading functions
namespace AGS3 {
namespace AGS {
namespace Shared {
struct AssetLibInfo;
struct Interaction;
struct InteractionScripts;
typedef std::shared_ptr<Interaction> PInteraction;
typedef std::shared_ptr<InteractionScripts> PInteractionScripts;
} // namespace Shared
} // namespace AGS
using AGS::Shared::PInteraction;
using AGS::Shared::PInteractionScripts;
using AGS::Shared::HGameFileError;
// TODO: split GameSetupStruct into struct used to hold loaded game data, and actual runtime object
struct GameSetupStruct : public GameSetupStructBase {
// This array is used only to read data into;
// font parameters are then put and queried in the fonts module
// TODO: split into installation params (used only when reading) and runtime params
std::vector<FontInfo> fonts;
InventoryItemInfo invinfo[MAX_INV]{};
std::vector<MouseCursor> mcurs;
std::vector<PInteraction> intrChar;
PInteraction intrInv[MAX_INV];
std::vector<PInteractionScripts> charScripts;
std::vector<PInteractionScripts> invScripts;
// TODO: why we do not use this in the engine instead of
// _G(loaded_game_file_version)?
int filever; // just used by editor
Shared::String compiled_with; // version of AGS this data was created by
char lipSyncFrameLetters[MAXLIPSYNCFRAMES][50];
AGS::Shared::PropertySchema propSchema;
std::vector<AGS::Shared::StringIMap> charProps;
AGS::Shared::StringIMap invProps[MAX_INV];
// NOTE: although the view names are stored in game data, they are never
// used, nor registered as script exports; numeric IDs are used to
// reference views instead.
std::vector<Shared::String> viewNames;
Shared::String invScriptNames[MAX_INV];
std::vector<Shared::String> dialogScriptNames;
char guid[MAX_GUID_LENGTH];
char saveGameFileExtension[MAX_SG_EXT_LENGTH];
// NOTE: saveGameFolderName is generally used to create game subdirs in common user directories
Shared::String saveGameFolderName;
int roomCount;
std::vector<int> roomNumbers;
std::vector<Shared::String> roomNames;
std::vector<ScriptAudioClip> audioClips;
std::vector<AudioClipType> audioClipTypes;
// A clip to play when player gains score in game
// TODO: find out why OPT_SCORESOUND option cannot be used to store this in >=3.2 games
int scoreClipID;
// number of accessible game audio channels (the ones under direct user control)
int numGameChannels = 0;
// backward-compatible channel limit that may be exported to script and reserved by audiotypes
int numCompatGameChannels = 0;
// TODO: I converted original array of sprite infos to vector here, because
// statistically in most games sprites go in long continious sequences with minimal
// gaps, and standard hash-map will have relatively big memory overhead compared.
// Of course vector will not behave very well if user has created e.g. only
// sprite #1 and sprite #1000000. For that reason I decided to still limit static
// sprite count to some reasonable number for the time being. Dynamic sprite IDs are
// added in sequence, so there won't be any issue with these.
// There could be other collection types, more optimal for this case. For example,
// we could use a kind of hash map containing fixed-sized arrays, where size of
// array is calculated based on key spread factor.
std::vector<SpriteInfo> SpriteInfos;
// Get game's native color depth (bits per pixel)
inline int GetColorDepth() const {
return color_depth * 8;
}
GameSetupStruct();
GameSetupStruct(GameSetupStruct &&gss) = default;
~GameSetupStruct();
GameSetupStruct &operator=(GameSetupStruct &&gss) = default;
void Free();
// [IKM] Game struct loading code is moved here from Engine's load_game_file
// function; for now it is not supposed to be called by Editor; although it
// is possible that eventually will be.
//
// Since reading game data is made in a bit inconvenient way I had to
// a) divide process into three functions (there's some extra stuff
// being read between them;
// b) use a helper struct to pass some arguments
//
// I also had to move BuildAudioClipArray from the engine and make it
// GameSetupStruct member.
//--------------------------------------------------------------------
// Do not call these directly
//------------------------------
// Part 1
void read_savegame_info(Shared::Stream *in, GameDataVersion data_ver);
void read_font_infos(Shared::Stream *in, GameDataVersion data_ver);
HGameFileError read_cursors(Shared::Stream *in);
void read_interaction_scripts(Shared::Stream *in, GameDataVersion data_ver);
void read_words_dictionary(Shared::Stream *in);
void ReadInvInfo(Shared::Stream *in);
void WriteInvInfo(Shared::Stream *out);
void ReadMouseCursors(Shared::Stream *in);
void WriteMouseCursors(Shared::Stream *out);
//------------------------------
// Part 2
void read_characters(Shared::Stream *in);
void read_lipsync(Shared::Stream *in, GameDataVersion data_ver);
void read_messages(Shared::Stream *in, const std::array<int32_t> &load_messages, GameDataVersion data_ver);
void ReadCharacters(Shared::Stream *in);
void WriteCharacters(Shared::Stream *out);
//------------------------------
// Part 3
HGameFileError read_customprops(Shared::Stream *in, GameDataVersion data_ver);
HGameFileError read_audio(Shared::Stream *in, GameDataVersion data_ver);
void read_room_names(Shared::Stream *in, GameDataVersion data_ver);
void ReadAudioClips(Shared::Stream *in, size_t count);
//--------------------------------------------------------------------
// Functions for reading and writing appropriate data from/to save game
void ReadFromSaveGame_v321(Shared::Stream *in);
void ReadFromSavegame(Shared::Stream *in);
void WriteForSavegame(Shared::Stream *out);
};
//=============================================================================
#if defined (OBSOLETE)
struct OldGameSetupStruct;
void ConvertOldGameStruct(OldGameSetupStruct *ogss, GameSetupStruct *gss);
#endif // OBSOLETE
// Finds an audio clip using legacy convention index
ScriptAudioClip *GetAudioClipForOldStyleNumber(GameSetupStruct &game, bool is_music, int num);
} // namespace AGS3
#endif

View File

@@ -0,0 +1,270 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/ac/character_info.h"
#include "ags/shared/ac/game_setup_struct_base.h"
#include "ags/shared/ac/game_setup_struct.h"
#include "ags/shared/ac/game_version.h"
#include "ags/shared/ac/words_dictionary.h"
#include "ags/shared/script/cc_script.h"
#include "ags/shared/util/stream.h"
#include "ags/shared/util/string_utils.h"
namespace AGS3 {
using namespace AGS::Shared;
GameSetupStructBase::GameSetupStructBase()
: numviews(0)
, numcharacters(0)
, playercharacter(-1)
, totalscore(0)
, numinvitems(0)
, numdialog(0)
, numdlgmessage(0)
, numfonts(0)
, color_depth(0)
, target_win(0)
, dialog_bullet(0)
, hotdot(0)
, hotdotouter(0)
, uniqueid(0)
, numgui(0)
, numcursors(0)
, default_lipsync_frame(0)
, invhotdotsprite(0)
, dict(nullptr)
, _resolutionType(kGameResolution_Undefined)
, _dataUpscaleMult(1)
, _screenUpscaleMult(1) {
memset(options, 0, sizeof(options));
memset(paluses, 0, sizeof(paluses));
memset(defpal, 0, sizeof(defpal));
memset(reserved, 0, sizeof(reserved));
}
GameSetupStructBase::~GameSetupStructBase() {
Free();
}
void GameSetupStructBase::Free() {
for (int i = 0; i < MAXGLOBALMES; ++i) {
messages[i].Free();
}
dict.reset();
chars.clear();
numcharacters = 0;
}
void GameSetupStructBase::SetDefaultResolution(GameResolutionType type) {
SetDefaultResolution(type, Size());
}
void GameSetupStructBase::SetDefaultResolution(Size size) {
SetDefaultResolution(kGameResolution_Custom, size);
}
void GameSetupStructBase::SetDefaultResolution(GameResolutionType type, Size size) {
// Calculate native res first then remember it
SetNativeResolution(type, size);
_defGameResolution = _gameResolution;
// Setup data resolution according to legacy settings (if set)
_dataResolution = _defGameResolution;
if (IsLegacyHiRes() && options[OPT_NATIVECOORDINATES] == 0) {
_dataResolution = _defGameResolution / HIRES_COORD_MULTIPLIER;
}
OnResolutionSet();
}
void GameSetupStructBase::SetNativeResolution(GameResolutionType type, Size game_res) {
if (type == kGameResolution_Custom) {
_resolutionType = kGameResolution_Custom;
_gameResolution = game_res;
_letterboxSize = _gameResolution;
} else {
_resolutionType = type;
_gameResolution = ResolutionTypeToSize(_resolutionType, IsLegacyLetterbox());
_letterboxSize = ResolutionTypeToSize(_resolutionType, false);
}
}
void GameSetupStructBase::SetGameResolution(GameResolutionType type) {
SetNativeResolution(type, Size());
OnResolutionSet();
}
void GameSetupStructBase::SetGameResolution(Size game_res) {
SetNativeResolution(kGameResolution_Custom, game_res);
OnResolutionSet();
}
void GameSetupStructBase::OnResolutionSet() {
// The final data-to-game multiplier is always set after actual game resolution (not default one)
if (!_dataResolution.IsNull())
_dataUpscaleMult = _gameResolution.Width / _dataResolution.Width;
else
_dataUpscaleMult = 1;
if (!_defGameResolution.IsNull())
_screenUpscaleMult = _gameResolution.Width / _defGameResolution.Width;
else
_screenUpscaleMult = 1;
_relativeUIMult = IsLegacyHiRes() ? HIRES_COORD_MULTIPLIER : 1;
}
void GameSetupStructBase::ReadFromFile(Stream *in, GameDataVersion game_ver, SerializeInfo &info) {
// NOTE: historically the struct was saved by dumping whole memory
// into the file stream, which added padding from memory alignment;
// here we mark the padding bytes, as they do not belong to actual data.
gamename.ReadCount(in, LEGACY_GAME_NAME_LENGTH);
in->ReadInt16(); // alignment padding to int32 (gamename: 50 -> 52 bytes)
in->ReadArrayOfInt32(options, MAX_OPTIONS);
if (game_ver < kGameVersion_340_4) { // TODO: this should probably be possible to deduce script API level
// using game data version and other options like OPT_STRICTSCRIPTING
options[OPT_BASESCRIPTAPI] = kScriptAPI_Undefined;
options[OPT_SCRIPTCOMPATLEV] = kScriptAPI_Undefined;
}
in->Read(&paluses[0], sizeof(paluses));
// colors are an array of chars
in->Read(&defpal[0], sizeof(defpal));
numviews = in->ReadInt32();
numcharacters = in->ReadInt32();
playercharacter = in->ReadInt32();
totalscore = in->ReadInt32();
numinvitems = in->ReadInt16();
in->ReadInt16(); // alignment padding to int32
numdialog = in->ReadInt32();
numdlgmessage = in->ReadInt32();
numfonts = in->ReadInt32();
color_depth = in->ReadInt32();
target_win = in->ReadInt32();
dialog_bullet = in->ReadInt32();
hotdot = static_cast<uint16_t>(in->ReadInt16());
hotdotouter = static_cast<uint16_t>(in->ReadInt16());
uniqueid = in->ReadInt32();
numgui = in->ReadInt32();
numcursors = in->ReadInt32();
GameResolutionType resolution_type = (GameResolutionType)in->ReadInt32();
Size game_size;
if (resolution_type == kGameResolution_Custom && game_ver >= kGameVersion_330) {
game_size.Width = in->ReadInt32();
game_size.Height = in->ReadInt32();
}
SetDefaultResolution(resolution_type, game_size);
default_lipsync_frame = in->ReadInt32();
invhotdotsprite = in->ReadInt32();
in->ReadArrayOfInt32(reserved, NUM_INTS_RESERVED);
info.ExtensionOffset = static_cast<uint32_t>(in->ReadInt32());
in->ReadArrayOfInt32(&info.HasMessages.front(), MAXGLOBALMES);
info.HasWordsDict = in->ReadInt32() != 0;
in->ReadInt32(); // globalscript (dummy 32-bit pointer value)
in->ReadInt32(); // chars (dummy 32-bit pointer value)
info.HasCCScript = in->ReadInt32() != 0;
}
void GameSetupStructBase::WriteToFile(Stream *out, const SerializeInfo &info) const {
// NOTE: historically the struct was saved by dumping whole memory
// into the file stream, which added padding from memory alignment;
// here we mark the padding bytes, as they do not belong to actual data.
gamename.WriteCount(out, LEGACY_GAME_NAME_LENGTH);
out->WriteInt16(0); // alignment padding to int32
out->WriteArrayOfInt32(options, MAX_OPTIONS);
out->Write(&paluses[0], sizeof(paluses));
// colors are an array of chars
out->Write(&defpal[0], sizeof(defpal));
out->WriteInt32(numviews);
out->WriteInt32(numcharacters);
out->WriteInt32(playercharacter);
out->WriteInt32(totalscore);
out->WriteInt16(numinvitems);
out->WriteInt16(0); // alignment padding to int32
out->WriteInt32(numdialog);
out->WriteInt32(numdlgmessage);
out->WriteInt32(numfonts);
out->WriteInt32(color_depth);
out->WriteInt32(target_win);
out->WriteInt32(dialog_bullet);
out->WriteInt16(static_cast<uint16_t>(hotdot));
out->WriteInt16(static_cast<uint16_t>(hotdotouter));
out->WriteInt32(uniqueid);
out->WriteInt32(numgui);
out->WriteInt32(numcursors);
out->WriteInt32(_resolutionType);
if (_resolutionType == kGameResolution_Custom) {
out->WriteInt32(_defGameResolution.Width);
out->WriteInt32(_defGameResolution.Height);
}
out->WriteInt32(default_lipsync_frame);
out->WriteInt32(invhotdotsprite);
out->WriteArrayOfInt32(reserved, 17);
for (int i = 0; i < MAXGLOBALMES; ++i) {
out->WriteInt32(!messages[i].IsEmpty() ? 1 : 0);
}
out->WriteInt32(dict ? 1 : 0);
out->WriteInt32(0); // globalscript (dummy 32-bit pointer value)
out->WriteInt32(0); // chars (dummy 32-bit pointer value)
out->WriteInt32(info.HasCCScript ? 1 : 0);
}
Size ResolutionTypeToSize(GameResolutionType resolution, bool letterbox) {
switch (resolution) {
case kGameResolution_Default:
case kGameResolution_320x200:
return letterbox ? Size(320, 240) : Size(320, 200);
case kGameResolution_320x240:
return Size(320, 240);
case kGameResolution_640x400:
return letterbox ? Size(640, 480) : Size(640, 400);
case kGameResolution_640x480:
return Size(640, 480);
case kGameResolution_800x600:
return Size(800, 600);
case kGameResolution_1024x768:
return Size(1024, 768);
case kGameResolution_1280x720:
return Size(1280, 720);
default:
return Size();
}
}
const char *GetScriptAPIName(ScriptAPIVersion v) {
switch (v) {
case kScriptAPI_v321: return "v3.2.1";
case kScriptAPI_v330: return "v3.3.0";
case kScriptAPI_v334: return "v3.3.4";
case kScriptAPI_v335: return "v3.3.5";
case kScriptAPI_v340: return "v3.4.0";
case kScriptAPI_v341: return "v3.4.1";
case kScriptAPI_v350: return "v3.5.0-alpha";
case kScriptAPI_v3507: return "v3.5.0-final";
case kScriptAPI_v351: return "v3.5.1";
case kScriptAPI_v360: return "v3.6.0-alpha";
case kScriptAPI_v36026: return "v3.6.0-final";
case kScriptAPI_v361: return "v3.6.1";
default: return "unknown";
}
}
} // namespace AGS3

View File

@@ -0,0 +1,283 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//
//=============================================================================
//
// GameSetupStructBase is a base class for main game data.
//
//=============================================================================
#ifndef AGS_SHARED_AC_GAME_SETUP_STRUCT_BASE_H
#define AGS_SHARED_AC_GAME_SETUP_STRUCT_BASE_H
#include "ags/lib/allegro.h" // RGB
#include "common/std/array.h"
#include "common/std/memory.h"
#include "common/std/vector.h"
#include "ags/shared/ac/game_version.h"
#include "ags/shared/ac/game_struct_defines.h"
#include "ags/shared/ac/words_dictionary.h"
#include "ags/shared/util/string.h"
#include "ags/globals.h"
namespace AGS3 {
// Forward declaration
namespace AGS {
namespace Shared {
class Stream;
} // namespace Shared
} // namespace AGS
using namespace AGS; // FIXME later
struct CharacterInfo;
struct ccScript;
struct GameSetupStructBase {
static const int LEGACY_GAME_NAME_LENGTH = 50;
static const int MAX_OPTIONS = 100;
static const int NUM_INTS_RESERVED = 16;
Shared::String gamename;
int32_t options[MAX_OPTIONS];
uint8_t paluses[256];
RGB defpal[256];
int numviews;
int numcharacters;
int playercharacter;
int totalscore;
int numinvitems;
int numdialog;
int numdlgmessage; // [DEPRECATED]
int numfonts;
int color_depth; // in bytes per pixel (ie. 1 or 2)
int target_win;
int dialog_bullet; // 0 for none, otherwise slot num of bullet point
int hotdot; // inv cursor hotspot dot color
int hotdotouter;
int uniqueid; // random key identifying the game
int numgui;
int numcursors;
int default_lipsync_frame; // used for unknown chars
int invhotdotsprite;
int32_t reserved[NUM_INTS_RESERVED];
String messages[MAXGLOBALMES];
std::unique_ptr<WordsDictionary> dict;
std::vector<CharacterInfo> chars;
std::vector<CharacterInfo2> chars2; // extended character fields
GameSetupStructBase();
GameSetupStructBase(GameSetupStructBase &&gss) = default;
~GameSetupStructBase();
GameSetupStructBase &operator=(GameSetupStructBase &&gss) = default;
void Free();
void SetDefaultResolution(GameResolutionType type);
void SetDefaultResolution(Size game_res);
void SetGameResolution(GameResolutionType type);
void SetGameResolution(Size game_res);
// Tells whether the serialized game data contains certain components
struct SerializeInfo {
bool HasCCScript = false;
bool HasWordsDict = false;
std::array<int32_t> HasMessages;
// File offset at which game data extensions begin
uint32_t ExtensionOffset = 0u;
SerializeInfo() {
HasMessages.resize(MAXGLOBALMES);
}
};
void ReadFromFile(Shared::Stream *in, GameDataVersion game_ver, SerializeInfo &info);
void WriteToFile(Shared::Stream *out, const SerializeInfo &info) const;
//
// ** On game resolution.
//
// Game resolution is a size of a native game screen in pixels.
// This is the "game resolution" that developer sets up in AGS Editor.
// It is in the same units in which sprite and font sizes are defined.
//
// Graphic renderer may scale and stretch game's frame as requested by
// player or system, which will not affect native coordinates in any way.
//
// ** Legacy upscale mode.
//
// In the past engine had a separation between logical and native screen
// coordinates and supported running games "upscaled". E.g. 320x200 games
// could be run as 640x400. This was not done by simply stretching final
// game's drawn frame to the larger window, but by multiplying all data
// containing coordinates and graphics either on load or real-time.
// Games of 640x400 and above were scripted and set up in coordinate units
// that were always x2 times smaller than the one developer chose.
// For example, choosing a 640x400 resolution would make game draw itself
// as 640x400, but all the game logic (object properties, script commands)
// would work in 320x200 (this also let run 640x400 downscaled to 320x200).
// Ignoring the obvious complications, the known benefit from such approach
// was that developers could supply separate sets of fonts and sprites for
// low-res and high-res modes.
// The 3rd generation of AGS still allows to achieve same effect by using
// backwards-compatible option (although it is not recommended except when
// importing and continuing old projects).
//
// In order to support this legacy behavior we have a set of functions for
// coordinate conversion. They are required to move from "data" resolution
// to "final game" resolution and back.
//
// Some of the script commands, as well as some internal engine data use
// coordinates in "game resolution" instead (this should be documented).
// In such case there's another conversion which translates these from
// default to actual resolution; e.g. when 320x200 game is run as 640x400
// they should be multiplied by 2.
//
// ** TODO.
//
// Truth be told, all this is still implemented incorrectly, because no one
// found time to rewrite the thing. The correct way would perhaps be:
// 1) treat old games as x2 lower resolution than they say.
// 2) support drawing particular sprites and texts in x2 higher resolution
// (assuming display resolution allows). The latter is potentially enabled
// by "sprite batches" system in the engine and will benefit new games too.
inline GameResolutionType GetResolutionType() const {
return _resolutionType;
}
// Get actual game's resolution
const Size &GetGameRes() const {
return _gameResolution;
}
// Get default resolution the game was created for;
// this is usually equal to GetGameRes except for legacy modes.
const Size &GetDefaultRes() const {
return _defGameResolution;
}
// Get data & script resolution;
// this is usually equal to GetGameRes except for legacy modes.
const Size &GetDataRes() const {
return _dataResolution;
}
// Get game data-->final game resolution coordinate multiplier
inline int GetDataUpscaleMult() const {
return _dataUpscaleMult;
}
// Get multiplier for various default UI sizes, meant to keep UI looks
// more or less readable in any game resolution.
// TODO: find a better solution for UI sizes, perhaps make variables.
inline int GetRelativeUIMult() const {
return _relativeUIMult;
}
// Get game default res-->final game resolution coordinate multiplier;
// used to convert coordinates from original game res to actual one
inline int GetScreenUpscaleMult() const {
return _screenUpscaleMult;
}
// Tells if game allows assets defined in relative resolution;
// that is - have to be converted to this game resolution type
inline bool AllowRelativeRes() const {
return options[OPT_RELATIVEASSETRES] != 0;
}
// Legacy definition of high and low game resolution.
// Used to determine certain hardcoded coordinate conversion logic, but
// does not make much sense today when the resolution is arbitrary.
inline bool IsLegacyHiRes() const {
if (_resolutionType == kGameResolution_Custom)
return (_gameResolution.Width * _gameResolution.Height) > (320 * 240);
return ::AGS3::IsLegacyHiRes(_resolutionType);
}
// Tells if data has coordinates in default game resolution
inline bool IsDataInNativeCoordinates() const {
return options[OPT_NATIVECOORDINATES] != 0;
}
// Tells if game runs in native letterbox mode (legacy option)
inline bool IsLegacyLetterbox() const {
return options[OPT_LETTERBOX] != 0;
}
// Get letterboxed frame size
const Size &GetLetterboxSize() const {
return _letterboxSize;
}
// Room region/hotspot masks are traditionally 1:1 of the room's size in
// low-resolution games and 1:2 of the room size in high-resolution games.
// This also means that mask relation to data resolution is 1:1 if the
// game uses low-res coordinates in script and 1:2 if high-res.
// Test if the game is built around old audio system
inline bool IsLegacyAudioSystem() const {
return _G(loaded_game_file_version) < kGameVersion_320;
}
// Returns the expected filename of a digital audio package
inline AGS::Shared::String GetAudioVOXName() const {
return IsLegacyAudioSystem() ? "music.vox" : "audio.vox";
}
// Returns a list of game options that are forbidden to change at runtime
inline static Common::Array<int> GetRestrictedOptions() {
return Common::Array<int> {{
OPT_DEBUGMODE, OPT_LETTERBOX, OPT_HIRES_FONTS, OPT_SPLITRESOURCES,
OPT_STRICTSCRIPTING, OPT_LEFTTORIGHTEVAL, OPT_COMPRESSSPRITES, OPT_STRICTSTRINGS,
OPT_NATIVECOORDINATES, OPT_SAFEFILEPATHS, OPT_DIALOGOPTIONSAPI, OPT_BASESCRIPTAPI,
OPT_SCRIPTCOMPATLEV, OPT_RELATIVEASSETRES, OPT_GAMETEXTENCODING, OPT_KEYHANDLEAPI,
OPT_CUSTOMENGINETAG
}};
}
private:
void SetDefaultResolution(GameResolutionType type, Size game_res);
void SetNativeResolution(GameResolutionType type, Size game_res);
void OnResolutionSet();
// Game's native resolution ID, used to init following values.
GameResolutionType _resolutionType;
// Determines game's default screen resolution. Use for the reference
// when comparing with actual screen resolution, which may be modified
// by certain overriding game modes.
Size _defGameResolution;
// Determines game's actual resolution.
Size _gameResolution;
// Determines resolution in which loaded data and script define coordinates
// and sizes (with very little exception).
Size _dataResolution;
// Letterboxed frame size. Used when old game is run in native letterbox
// mode. In all other situations is equal to game's resolution.
Size _letterboxSize;
// Game logic to game resolution coordinate factor
int _dataUpscaleMult;
// Multiplier for various UI drawin sizes, meant to keep UI elements readable
int _relativeUIMult;
// Game default resolution to actual game resolution factor
int _screenUpscaleMult;
};
} // namespace AGS3
#endif

View File

@@ -0,0 +1,296 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_AC_GAME_STRUCT_DEFINES_H
#define AGS_SHARED_AC_GAME_STRUCT_DEFINES_H
#include "ags/shared/util/geometry.h"
#include "ags/shared/core/types.h"
namespace AGS3 {
#define PAL_GAMEWIDE 0
#define PAL_LOCKED 1
#define PAL_BACKGROUND 2
#define MAXGLOBALMES 500
#define GLOBALMESLENGTH 500
#define MAXLANGUAGE 5
#define LEGACY_MAX_FONTS 30
// General game options
#define OPT_DEBUGMODE 0
#define OPT_SCORESOUND 1
#define OPT_WALKONLOOK 2
#define OPT_DIALOGIFACE 3
#define OPT_ANTIGLIDE 4
#define OPT_TWCUSTOM 5
#define OPT_DIALOGGAP 6
#define OPT_NOSKIPTEXT 7
#define OPT_DISABLEOFF 8
#define OPT_ALWAYSSPCH 9
#define OPT_SPEECHTYPE 10
#define OPT_PIXPERFECT 11
#define OPT_NOWALKMODE 12
#define OPT_LETTERBOX 13
#define OPT_FIXEDINVCURSOR 14
#define OPT_NOLOSEINV 15
#define OPT_HIRES_FONTS 16
#define OPT_SPLITRESOURCES 17
#define OPT_ROTATECHARS 18
#define OPT_FADETYPE 19
#define OPT_HANDLEINVCLICKS 20
#define OPT_MOUSEWHEEL 21
#define OPT_DIALOGNUMBERED 22
#define OPT_DIALOGUPWARDS 23
#define OPT_CROSSFADEMUSIC 24
#define OPT_ANTIALIASFONTS 25
#define OPT_THOUGHTGUI 26
#define OPT_TURNTOFACELOC 27
#define OPT_RIGHTLEFTWRITE 28 // right-to-left text writing
#define OPT_DUPLICATEINV 29 // if they have 2 of the item, draw it twice
#define OPT_SAVESCREENSHOT 30
#define OPT_PORTRAITSIDE 31
#define OPT_STRICTSCRIPTING 32 // don't allow MoveCharacter-style commands
#define OPT_LEFTTORIGHTEVAL 33 // left-to-right operator evaluation
#define OPT_COMPRESSSPRITES 34 // sprite compression type (None, RLE, LZW, Deflate)
#define OPT_STRICTSTRINGS 35 // don't allow old-style strings, for reference only
#define OPT_NEWGUIALPHA 36 // alpha blending method when drawing GUI and controls
#define OPT_RUNGAMEDLGOPTS 37
#define OPT_NATIVECOORDINATES 38 // defines coordinate relation between game logic and game screen
#define OPT_GLOBALTALKANIMSPD 39
#define OPT_HIGHESTOPTION_321 39
#define OPT_SPRITEALPHA 40 // alpha blending method when drawing images on DrawingSurface
#define OPT_SAFEFILEPATHS 41 // restricted file path in script (not writing to the game dir, etc)
#define OPT_DIALOGOPTIONSAPI 42 // version of dialog options API (-1 for pre-3.4.0 API)
#define OPT_BASESCRIPTAPI 43 // version of the Script API (ScriptAPIVersion) used to compile game script
#define OPT_SCRIPTCOMPATLEV 44 // level of API compatibility (ScriptAPIVersion) used to compile game script
#define OPT_RENDERATSCREENRES 45 // scale sprites at the (final) screen resolution
#define OPT_RELATIVEASSETRES 46 // relative asset resolution mode (where sprites are resized to match game type)
#define OPT_WALKSPEEDABSOLUTE 47 // if movement speeds are independent of walkable mask resolution
#define OPT_CLIPGUICONTROLS 48 // clip drawn gui control contents to the control's rectangle
#define OPT_GAMETEXTENCODING 49 // how the text in the game data should be interpreted
#define OPT_KEYHANDLEAPI 50 // key handling mode (old/new)
#define OPT_CUSTOMENGINETAG 51 // custom engine tag (for overriding behavior)
#define OPT_SCALECHAROFFSETS 52 // apply character scaling to the sprite offsets (z, locked offs)
#define OPT_HIGHESTOPTION OPT_SCALECHAROFFSETS
#define OPT_NOMODMUSIC 98 // [DEPRECATED]
#define OPT_LIPSYNCTEXT 99
#define CUSTOMENG_NONE 0
#define CUSTOMENG_DRACONIAN 1 // Draconian Edition
#define CUSTOMENG_CLIFFTOP 2 // Clifftop Games
// Sierra-style portrait position style
#define PORTRAIT_LEFT 0
#define PORTRAIT_RIGHT 1
#define PORTRAIT_ALTERNATE 2
#define PORTRAIT_XPOSITION 3
// Room transition style
#define FADE_NORMAL 0
#define FADE_INSTANT 1
#define FADE_DISSOLVE 2
#define FADE_BOXOUT 3
#define FADE_CROSSFADE 4
#define FADE_LAST 4 // this should equal the last one
// Legacy font flags
//#define FFLG_LEGACY_NOSCALE 0x01 // TODO: is this from legacy format, ever used?
#define FFLG_LEGACY_SIZEMASK 0x3f
#define MAX_LEGACY_FONT_SIZE 63
// Contemporary font flags
#define FFLG_SIZEMULTIPLIER 0x01 // size data means multiplier
#define FFLG_DEFLINESPACING 0x02 // linespacing derived from the font height
// Font load flags, primarily for backward compatibility:
// REPORTNOMINALHEIGHT: get_font_height should return nominal font's height,
// eq to "font size" parameter, otherwise returns real pixel height.
#define FFLG_REPORTNOMINALHEIGHT 0x04
// ASCENDFIXUP: do the TTF ascender fixup, where font's ascender is resized
// to the nominal font's height.
#define FFLG_ASCENDERFIXUP 0x08
// Collection of flags defining fully backward compatible TTF fixup
#define FFLG_TTF_BACKCOMPATMASK (FFLG_REPORTNOMINALHEIGHT | FFLG_ASCENDERFIXUP)
// Collection of flags defining font's load mode
#define FFLG_LOADMODEMASK (FFLG_REPORTNOMINALHEIGHT | FFLG_ASCENDERFIXUP)
// Font outline types
#define FONT_OUTLINE_NONE -1
#define FONT_OUTLINE_AUTO -10
#define DIALOG_OPTIONS_HIGHLIGHT_COLOR_DEFAULT 14 // Yellow
// MAXVIEWNAMELENGTH comes from unknown old engine version
#define LEGACY_MAXVIEWNAMELENGTH 15
#define MAXLIPSYNCFRAMES 20
#define MAX_GUID_LENGTH 40
#define MAX_SG_EXT_LENGTH 20
#define LEGACY_MAX_SG_FOLDER_LEN 50
enum GameResolutionType {
kGameResolution_Undefined = -1,
// definition of 320x200 in very old versions of the engine (somewhere pre-2.56)
kGameResolution_Default = 0,
kGameResolution_320x200 = 1,
kGameResolution_320x240 = 2,
kGameResolution_640x400 = 3,
kGameResolution_640x480 = 4,
kGameResolution_800x600 = 5,
kGameResolution_1024x768 = 6,
kGameResolution_1280x720 = 7,
kGameResolution_Custom = 8,
kNumGameResolutions,
kGameResolution_LastLoRes = kGameResolution_320x240,
kGameResolution_FirstHiRes = kGameResolution_640x400
};
inline bool IsLegacyHiRes(GameResolutionType resolution) {
return resolution > kGameResolution_LastLoRes;
}
Size ResolutionTypeToSize(GameResolutionType resolution, bool letterbox = false);
// Automatic numbering of dialog options (OPT_DIALOGNUMBERED)
enum DialogOptionNumbering {
kDlgOptNoNumbering = -1,
kDlgOptKeysOnly = 0, // implicit key shortcuts
kDlgOptNumbering = 1 // draw option indices and use key shortcuts
};
// Version of the script api (OPT_BASESCRIPTAPI and OPT_SCRIPTCOMPATLEV).
// If the existing script function meaning had changed, that may be
// possible to find out which implementation to use by checking one of those
// two options.
// NOTE: please remember that those values are valid only for games made with
// 3.4.0 final and above.
enum ScriptAPIVersion {
kScriptAPI_Undefined = INT32_MIN,
kScriptAPI_v321 = 0,
kScriptAPI_v330 = 1,
kScriptAPI_v334 = 2,
kScriptAPI_v335 = 3,
kScriptAPI_v340 = 4,
kScriptAPI_v341 = 5,
kScriptAPI_v350 = 6,
kScriptAPI_v3507 = 7,
kScriptAPI_v351 = 8,
kScriptAPI_v360 = 3060000,
kScriptAPI_v36026 = 3060026,
kScriptAPI_v361 = 3060100,
kScriptAPI_Current = kScriptAPI_v361
};
extern const char *GetScriptAPIName(ScriptAPIVersion v);
// Determines whether the graphics renderer should scale sprites at the final
// screen resolution, as opposed to native resolution
enum RenderAtScreenRes {
kRenderAtScreenRes_UserDefined = 0,
kRenderAtScreenRes_Enabled = 1,
kRenderAtScreenRes_Disabled = 2,
};
// Method to use when blending two sprites with alpha channel
enum GameSpriteAlphaRenderingStyle {
kSpriteAlphaRender_Legacy = 0,
kSpriteAlphaRender_Proper
};
// Method to use when blending two GUI elements with alpha channel
enum GameGuiAlphaRenderingStyle {
kGuiAlphaRender_Legacy = 0,
kGuiAlphaRender_AdditiveAlpha,
kGuiAlphaRender_Proper
};
// Sprite flags
// SERIALIZATION NOTE: serialized as 8-bit in game data and legacy saves
// serialized as 32-bit in new saves (for dynamic sprites only).
#define SPF_HIRES 0x01 // sized for high native resolution (legacy option)
#define SPF_HICOLOR 0x02 // is 16-bit (UNUSED)
#define SPF_DYNAMICALLOC 0x04 // created by runtime script
#define SPF_TRUECOLOR 0x08 // is 32-bit (UNUSED)
#define SPF_ALPHACHANNEL 0x10 // has alpha-channel
#define SPF_VAR_RESOLUTION 0x20 // variable resolution (refer to SPF_HIRES)
#define SPF_HADALPHACHANNEL 0x80 // the saved sprite on disk has one
#define SPF_OBJECTOWNED 0x0100 // owned by a game object (not created in user script)
// General information about sprite (properties, size)
struct SpriteInfo {
int Width = 0;
int Height = 0;
uint32_t Flags = 0u; // SPF_* flags
SpriteInfo() = default;
SpriteInfo(int w, int h, uint32_t flags) : Width(w), Height(h), Flags(flags) {}
inline Size GetResolution() const { return Size(Width, Height); }
// Gets if sprite is created at runtime (by engine, or a script command)
inline bool IsDynamicSprite() const { return (Flags & SPF_DYNAMICALLOC) != 0; }
//
// Legacy game support
//
// Gets if sprite should adjust its base size depending on game's resolution
inline bool IsRelativeRes() const {
return (Flags & SPF_VAR_RESOLUTION) != 0;
}
// Gets if sprite belongs to high resolution; hi-res sprites should be
// downscaled in low-res games, and low-res sprites should be upscaled
// in hi-res games
inline bool IsLegacyHiRes() const {
return (Flags & SPF_HIRES) != 0;
}
};
// Various font parameters, defining and extending font rendering behavior.
// While FontRenderer object's main goal is to render single line of text at
// the strictly determined position on canvas, FontInfo may additionally
// provide instructions on adjusting drawing position, as well as arranging
// multiple lines, and similar cases.
struct FontInfo {
enum AutoOutlineStyle : int {
kSquared = 0,
kRounded = 1,
};
// General font's loading and rendering flags
uint32_t Flags;
// Nominal font import size (in pixels)
int Size;
// Factor to multiply base font size by
int SizeMultiplier;
// Outlining font index, or auto-outline flag
int Outline;
// Custom vertical render offset, used mainly for fixing broken fonts
int YOffset;
// Custom line spacing between two lines of text (0 = use font height)
int LineSpacing;
// When automatic outlining, thickness of the outline (0 = no auto outline)
int AutoOutlineThickness;
// When automatic outlining, style of the outline
AutoOutlineStyle AutoOutlineStyle;
FontInfo();
};
} // namespace AGS3
#endif

View File

@@ -0,0 +1,175 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// Game version constants and information
//
//=============================================================================
#ifndef AGS_SHARED_AC_GAME_VERSION_H
#define AGS_SHARED_AC_GAME_VERSION_H
namespace AGS3 {
/*
Game data versions and changes:
-------------------------------
12 : 2.3 + 2.4
Versions above are incompatible at the moment.
18 : 2.5.0
19 : 2.5.1 + 2.52
20 : 2.5.3
Lip sync data added.
21 : 2.5.4
22 : 2.5.5
Variable number of sprites.
24 : 2.5.6
25 : 2.6.0
Encrypted global messages and dialogs.
26 : 2.6.1
Wait() must be called with parameter > 0
GetRegionAt() clips the input values to the screen size
Color 0 now means transparent instead of black for text windows
SetPlayerCharacter() does nothing if the new character is already the player character.
27 : 2.6.2
Script modules. Fixes bug in the inventory display.
Clickable GUI is selected with regard for the drawing order.
Pointer to the "player" variable is now accessed via a dynamic object.
31 : 2.7.0
32 : 2.7.2
35 : 3.0.0
Room names are serialized when game is compiled in "debug" mode.
36 : 3.0.1
Interactions are now scripts. The number for "not set" changed from 0 to -1 for
a lot of variables (views, sounds).
Deprecated switch between low-res and high-res native coordinates.
37 : 3.1.0
Dialogs are now scripts. New character animation speed.
39 : 3.1.1
Individual character speech animation speed.
40 : 3.1.2
Audio clips
41 : 3.2.0
42 : 3.2.1
43 : 3.3.0
Added few more game options.
44 : 3.3.1
Added custom dialog option highlight colour.
45 : 3.4.0.1
Support for custom game resolution.
46 : 3.4.0.2-.3
Audio playback speed.
Custom dialog option rendering extension.
47 : 3.4.0.4
Custom properties changed at runtime.
Ambient lighting
48 : 3.4.1
OPT_RENDERATSCREENRES, extended engine caps check, font vertical offset.
49 : 3.4.1.2
Font custom line spacing.
50 : 3.5.0.8
Sprites have "real" resolution. Expanded FontInfo data format.
Option to allow legacy relative asset resolutions.
3.6.0 :
Format value is defined as AGS version represented as NN,NN,NN,NN.
Fonts have adjustable outline
3.6.0.11:
New font load flags, control backward compatible font behavior
3.6.0.16:
Idle animation speed, modifiable hotspot names, fixed video frame
3.6.0.21:
Some adjustments to gui text alignment.
3.6.1:
In RTL mode all text is reversed, not only wrappable (labels etc).
3.6.1.10:
Disabled automatic SetRestartPoint.
3.6.1.14:
Extended game object names, resolving hard length limits.
*/
enum GameDataVersion {
kGameVersion_Undefined = 0,
kGameVersion_230 = 12,
kGameVersion_240 = 12,
kGameVersion_250 = 18,
kGameVersion_251 = 19, // same as 2.52
kGameVersion_253 = 20,
kGameVersion_254 = 21,
kGameVersion_255 = 22,
kGameVersion_256 = 24,
kGameVersion_260 = 25,
kGameVersion_261 = 26,
kGameVersion_262 = 27,
kGameVersion_270 = 31,
kGameVersion_272 = 32,
kGameVersion_300 = 35,
kGameVersion_301 = 36,
kGameVersion_310 = 37,
kGameVersion_311 = 39,
kGameVersion_312 = 40,
kGameVersion_320 = 41,
kGameVersion_321 = 42,
kGameVersion_330 = 43,
kGameVersion_331 = 44,
kGameVersion_340_1 = 45,
kGameVersion_340_2 = 46,
kGameVersion_340_4 = 47,
kGameVersion_341 = 48,
kGameVersion_341_2 = 49,
kGameVersion_350 = 50,
kGameVersion_360 = 3060000,
kGameVersion_360_11 = 3060011,
kGameVersion_360_16 = 3060016,
kGameVersion_360_21 = 3060021,
kGameVersion_361 = 3060100,
kGameVersion_361_10 = 3060110,
kGameVersion_361_14 = 3060114,
kGameVersion_Current = kGameVersion_361_14
};
} // namespace AGS3
#endif

View File

@@ -0,0 +1,47 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_AC_INTERFACE_BUTTON_H
#define AGS_SHARED_AC_INTERFACE_BUTTON_H
#include "ags/shared/core/types.h"
#if defined (OBSOLETE)
namespace AGS3 {
#define MAXBUTTON 20
#define IBFLG_ENABLED 1
#define IBFLG_INVBOX 2
struct InterfaceButton {
int x, y, pic, overpic, pushpic, leftclick;
int rightclick; // if inv, then leftclick = wid, rightclick = hit
int reserved_for_future;
int8 flags;
void set(int xx, int yy, int picc, int overpicc, int actionn);
};
} // namespace AGS3
#endif // OBSOLETE
#endif

View File

@@ -0,0 +1,51 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_AC_INTERFACE_ELEMENT_H
#define AGS_SHARED_AC_INTERFACE_ELEMENT_H
#if defined (OBSOLETE)
#include "ags/shared/ac/interface_button.h" // InterfaceButton
namespace AGS3 {
// this struct should go in a Game struct, not the room structure.
struct InterfaceElement {
int x, y, x2, y2;
int bgcol, fgcol, bordercol;
int vtextxp, vtextyp, vtextalign; // X & Y relative to topleft of interface
char vtext[40];
int numbuttons;
InterfaceButton button[MAXBUTTON];
int flags;
int reserved_for_future;
int popupyp; // pops up when _G(mousey) < this
int8 popup; // does it pop up? (like sierra icon bar)
int8 on;
InterfaceElement();
};
} // namespace AGS3
#endif // OBSOLETE
#endif

View File

@@ -0,0 +1,66 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/ac/inventory_item_info.h"
#include "ags/shared/util/stream.h"
#include "ags/shared/util/string_utils.h"
namespace AGS3 {
using namespace AGS::Shared;
void InventoryItemInfo::ReadFromFile(Stream *in) {
name.ReadCount(in, LEGACY_MAX_INVENTORY_NAME_LENGTH);
in->Seek(3); // alignment padding to int32
pic = in->ReadInt32();
cursorPic = in->ReadInt32();
hotx = in->ReadInt32();
hoty = in->ReadInt32();
in->ReadArrayOfInt32(reserved, 5);
flags = in->ReadInt8();
in->Seek(3); // alignment padding to int32
}
void InventoryItemInfo::WriteToFile(Stream *out) {
name.WriteCount(out, LEGACY_MAX_INVENTORY_NAME_LENGTH);
out->WriteByteCount(0, 3); // alignment padding to int32
out->WriteInt32(pic);
out->WriteInt32(cursorPic);
out->WriteInt32(hotx);
out->WriteInt32(hoty);
out->WriteArrayOfInt32(reserved, 5);
out->WriteInt8(flags);
out->WriteByteCount(0, 3); // alignment padding to int32
}
void InventoryItemInfo::ReadFromSavegame(Stream *in) {
name = StrUtil::ReadString(in);
pic = in->ReadInt32();
cursorPic = in->ReadInt32();
}
void InventoryItemInfo::WriteToSavegame(Stream *out) const {
StrUtil::WriteString(name, out);
out->WriteInt32(pic);
out->WriteInt32(cursorPic);
}
} // namespace AGS3

View File

@@ -0,0 +1,56 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_AC_INVENTORY_ITEM_INFO_H
#define AGS_SHARED_AC_INVENTORY_ITEM_INFO_H
#include "ags/shared/core/types.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class Stream;
} // namespace Shared
} // namespace AGS
using namespace AGS::Shared;
#define IFLG_STARTWITH 1
#define LEGACY_MAX_INVENTORY_NAME_LENGTH 25
struct InventoryItemInfo {
String name;
int pic;
int cursorPic, hotx, hoty;
int32_t reserved[5];
uint8_t flags; // IFLG_STARTWITH
void ReadFromFile(Stream *in);
void WriteToFile(Stream *out);
void ReadFromSavegame(Stream *in);
void WriteToSavegame(Stream *out) const;
};
} // namespace AGS3
#endif

View File

@@ -0,0 +1,39 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/ac/keycode.h"
namespace AGS3 {
eAGSKeyCode AGSKeyToScriptKey(eAGSKeyCode keycode) {
// Script API requires strictly capital letters, if this is a small letter - capitalize it
return (keycode >= 'a' && keycode <= 'z') ?
static_cast<eAGSKeyCode>(keycode - 'a' + 'A') : keycode;
}
char AGSKeyToText(eAGSKeyCode keycode) {
// support only printable characters (128-255 are chars from extended fonts)
if (keycode >= 32 && keycode < 256)
return static_cast<char>(keycode);
return 0;
}
} // namespace AGS3

View File

@@ -0,0 +1,323 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_AC_KEYCODE_H
#define AGS_SHARED_AC_KEYCODE_H
#include "ags/shared/core/platform.h"
#include "ags/shared/core/types.h"
namespace AGS3 {
#define EXTENDED_KEY_CODE ('\0')
#define EXTENDED_KEY_CODE_MACOS ('?')
// Constant used to define Alt+Key codes
#define AGS_EXT_KEY_SHIFT 300
#define AGS_EXT_KEY_ALPHA(key) (AGS_EXT_KEY_SHIFT + (key - eAGSKeyCodeCtrlA) + 1)
// These are based on eKeyCode values in AGS Script.
// The actual values are based on scan codes of the old backend (allegro 3 and/or 4),
// which in turn mostly match ASCII values (at least for ones below 128), including
// Ctrl + letter combination codes.
// More codes are added at much higher ranges, for example Alt + letter combo codes
// are defined as 300 + letter's order.
// It should be specifically noted that eAGSKeyCode is directly conversible to ASCII
// at the range of 1 - 128, and AGS script makes use of this.
// Another important thing to note is that letter codes are always sent into script
// callbacks (like "on_key_pressed") in capitalized form, and that's how they are
// declared in script API (that's why in these callbacks user would have to check
// the Shift key state if they want to know if it's A or Shift + A).
enum eAGSKeyCode {
eAGSKeyCodeNone = 0,
eAGSKeyCodeCtrlA = 1,
eAGSKeyCodeCtrlB = 2,
eAGSKeyCodeCtrlC = 3,
eAGSKeyCodeCtrlD = 4,
eAGSKeyCodeCtrlE = 5,
eAGSKeyCodeCtrlF = 6,
eAGSKeyCodeCtrlG = 7,
eAGSKeyCodeCtrlH = 8,
eAGSKeyCodeCtrlI = 9,
eAGSKeyCodeCtrlJ = 10,
eAGSKeyCodeCtrlK = 11,
eAGSKeyCodeCtrlL = 12,
eAGSKeyCodeCtrlM = 13,
eAGSKeyCodeCtrlN = 14,
eAGSKeyCodeCtrlO = 15,
eAGSKeyCodeCtrlP = 16,
eAGSKeyCodeCtrlQ = 17,
eAGSKeyCodeCtrlR = 18,
eAGSKeyCodeCtrlS = 19,
eAGSKeyCodeCtrlT = 20,
eAGSKeyCodeCtrlU = 21,
eAGSKeyCodeCtrlV = 22,
eAGSKeyCodeCtrlW = 23,
eAGSKeyCodeCtrlX = 24,
eAGSKeyCodeCtrlY = 25,
eAGSKeyCodeCtrlZ = 26,
eAGSKeyCodeBackspace = 8, // matches Ctrl + H
eAGSKeyCodeTab = 9, // matches Ctrl + I
eAGSKeyCodeReturn = 13, // matches Ctrl + M
eAGSKeyCodeEscape = 27,
/* printable chars - from eAGSKeyCodeSpace to eAGSKeyCode_z */
eAGSKeyCodeSpace = 32,
eAGSKeyCodeExclamationMark = 33,
eAGSKeyCodeDoubleQuote = 34,
eAGSKeyCodeHash = 35,
eAGSKeyCodeDollar = 36,
eAGSKeyCodePercent = 37,
eAGSKeyCodeAmpersand = 38,
eAGSKeyCodeSingleQuote = 39,
eAGSKeyCodeOpenParenthesis = 40,
eAGSKeyCodeCloseParenthesis = 41,
eAGSKeyCodeAsterisk = 42,
eAGSKeyCodePlus = 43,
eAGSKeyCodeComma = 44,
eAGSKeyCodeHyphen = 45,
eAGSKeyCodePeriod = 46,
eAGSKeyCodeForwardSlash = 47,
eAGSKeyCode0 = 48,
eAGSKeyCode1 = 49,
eAGSKeyCode2 = 50,
eAGSKeyCode3 = 51,
eAGSKeyCode4 = 52,
eAGSKeyCode5 = 53,
eAGSKeyCode6 = 54,
eAGSKeyCode7 = 55,
eAGSKeyCode8 = 56,
eAGSKeyCode9 = 57,
eAGSKeyCodeColon = 58,
eAGSKeyCodeSemiColon = 59,
eAGSKeyCodeLessThan = 60,
eAGSKeyCodeEquals = 61,
eAGSKeyCodeGreaterThan = 62,
eAGSKeyCodeQuestionMark = 63,
eAGSKeyCodeAt = 64, // '@'
/* Notice that default letter codes match capital ASCII letters */
eAGSKeyCodeA = 65, // 'A'
eAGSKeyCodeB = 66, // 'B', etc
eAGSKeyCodeC = 67,
eAGSKeyCodeD = 68,
eAGSKeyCodeE = 69,
eAGSKeyCodeF = 70,
eAGSKeyCodeG = 71,
eAGSKeyCodeH = 72,
eAGSKeyCodeI = 73,
eAGSKeyCodeJ = 74,
eAGSKeyCodeK = 75,
eAGSKeyCodeL = 76,
eAGSKeyCodeM = 77,
eAGSKeyCodeN = 78,
eAGSKeyCodeO = 79,
eAGSKeyCodeP = 80,
eAGSKeyCodeQ = 81,
eAGSKeyCodeR = 82,
eAGSKeyCodeS = 83,
eAGSKeyCodeT = 84,
eAGSKeyCodeU = 85,
eAGSKeyCodeV = 86,
eAGSKeyCodeW = 87,
eAGSKeyCodeX = 88,
eAGSKeyCodeY = 89,
eAGSKeyCodeZ = 90, // 'Z'
eAGSKeyCodeOpenBracket = 91,
eAGSKeyCodeBackSlash = 92,
eAGSKeyCodeCloseBracket = 93,
eAGSKeyCodeCaret = 94, // '^'
eAGSKeyCodeUnderscore = 95,
eAGSKeyCodeBackquote = 96, // '`'
/* Small ASCII letter codes are declared here for consistency, but unused in script callbacks */
eAGSKeyCode_a = 97, // 'a'
eAGSKeyCode_b = 98, // 'b', etc
eAGSKeyCode_c = 99,
eAGSKeyCode_d = 100,
eAGSKeyCode_e = 101,
eAGSKeyCode_f = 102,
eAGSKeyCode_g = 103,
eAGSKeyCode_h = 104,
eAGSKeyCode_i = 105,
eAGSKeyCode_j = 106,
eAGSKeyCode_k = 107,
eAGSKeyCode_l = 108,
eAGSKeyCode_m = 109,
eAGSKeyCode_n = 110,
eAGSKeyCode_o = 111,
eAGSKeyCode_p = 112,
eAGSKeyCode_q = 113,
eAGSKeyCode_r = 114,
eAGSKeyCode_s = 115,
eAGSKeyCode_t = 116,
eAGSKeyCode_u = 117,
eAGSKeyCode_v = 118,
eAGSKeyCode_w = 119,
eAGSKeyCode_x = 120,
eAGSKeyCode_y = 121,
eAGSKeyCode_z = 122, // 'z'
/* extended symbol codes */
eAGSKeyCodeF1 = AGS_EXT_KEY_SHIFT + 59,
eAGSKeyCodeF2 = AGS_EXT_KEY_SHIFT + 60,
eAGSKeyCodeF3 = AGS_EXT_KEY_SHIFT + 61,
eAGSKeyCodeF4 = AGS_EXT_KEY_SHIFT + 62,
eAGSKeyCodeF5 = AGS_EXT_KEY_SHIFT + 63,
eAGSKeyCodeF6 = AGS_EXT_KEY_SHIFT + 64,
eAGSKeyCodeF7 = AGS_EXT_KEY_SHIFT + 65,
eAGSKeyCodeF8 = AGS_EXT_KEY_SHIFT + 66,
eAGSKeyCodeF9 = AGS_EXT_KEY_SHIFT + 67,
eAGSKeyCodeF10 = AGS_EXT_KEY_SHIFT + 68,
eAGSKeyCodeF11 = AGS_EXT_KEY_SHIFT + 133,
eAGSKeyCodeF12 = AGS_EXT_KEY_SHIFT + 134,
eAGSKeyCodeHome = AGS_EXT_KEY_SHIFT + 71,
eAGSKeyCodeUpArrow = AGS_EXT_KEY_SHIFT + 72,
eAGSKeyCodePageUp = AGS_EXT_KEY_SHIFT + 73,
eAGSKeyCodeLeftArrow = AGS_EXT_KEY_SHIFT + 75,
eAGSKeyCodeNumPad5 = AGS_EXT_KEY_SHIFT + 76,
eAGSKeyCodeRightArrow = AGS_EXT_KEY_SHIFT + 77,
eAGSKeyCodeEnd = AGS_EXT_KEY_SHIFT + 79,
eAGSKeyCodeDownArrow = AGS_EXT_KEY_SHIFT + 80,
eAGSKeyCodePageDown = AGS_EXT_KEY_SHIFT + 81,
eAGSKeyCodeInsert = AGS_EXT_KEY_SHIFT + 82,
eAGSKeyCodeDelete = AGS_EXT_KEY_SHIFT + 83,
// [sonneveld] These are only used by debugging and abort keys.
// They're based on allegro4 codes ...
eAGSKeyCodeAltV = AGS_EXT_KEY_ALPHA(eAGSKeyCodeV),
eAGSKeyCodeAltX = AGS_EXT_KEY_ALPHA(eAGSKeyCodeX),
eAGSKeyCodeAltY = AGS_EXT_KEY_ALPHA(eAGSKeyCodeY),
eAGSKeyCodeAltZ = AGS_EXT_KEY_ALPHA(eAGSKeyCodeZ),
// The beginning of "service key list": mod keys and other special keys
// not normally intended to affect the default game logic
eAGSKeyCode_FirstServiceKey = 391,
// not certain if necessary anymore (and not certain what was the origin of this value)
eAGSKeyCodeAltTab = AGS_EXT_KEY_SHIFT + 99,
// Mod-key codes
// *probably* made-up numbers, not derived from allegro scan codes.
eAGSKeyCodeLShift = 403,
eAGSKeyCodeRShift = 404,
eAGSKeyCodeLCtrl = 405,
eAGSKeyCodeRCtrl = 406,
eAGSKeyCodeLAlt = 407,
// [sonneveld]
// The following are the AGS_EXT_KEY_SHIFT, derived from applying arithmetic to the original keycodes.
// These do not have a corresponding ags key enum, do not appear in the manual and may not be accessible because of OS contraints.
eAGSKeyCodeRAlt = 420,
// TODO: judging that above works (at least on Win), following might also work,
// but idk which ones may be necessary; still keeping here this excerpt from an old code
// if they'd want to be restored (also add them to script API then!).
// Also see allegro 4's keyboard.h, where these were declared.
/*
case 392: __allegro_KEY_PRTSCR
case 393: __allegro_KEY_PAUSE
case 394: __allegro_KEY_ABNT_C1 // The ABNT_C1 (Brazilian) key
case 395: __allegro_KEY_YEN)
case 396: __allegro_KEY_KANA
case 397: __allegro_KEY_CONVERT
case 398: __allegro_KEY_NOCONVERT
case 400: __allegro_KEY_CIRCUMFLEX
case 402: __allegro_KEY_KANJI
case 421: __allegro_KEY_LWIN
case 422: __allegro_KEY_RWIN
case 423: __allegro_KEY_MENU
case 424: __allegro_KEY_SCRLOCK
case 425: __allegro_KEY_NUMLOCK
case 426: __allegro_KEY_CAPSLOCK
*/
// Mask defines the key code position if packed in the int32;
// takes only 12 bits, as minimal necessary to accommodate historical codes.
eAGSKeyMask = 0x0FFF
};
// AGS key modifiers
enum eAGSKeyMod {
eAGSModLShift = 0x00010000,
eAGSModRShift = 0x00020000,
eAGSModLCtrl = 0x00040000,
eAGSModRCtrl = 0x00080000,
eAGSModLAlt = 0x00100000,
eAGSModRAlt = 0x00200000,
eAGSModNum = 0x00400000,
eAGSModCaps = 0x00800000,
// Mask defines the key mod position if packed in the int32;
// the upper 8 bits are reserved for "input type" codes;
// potentially may take 4 bits below (4th pos), as KeyMask takes only 12.
eAGSModMask = 0x00FF0000
};
// Combined key code and a textual representation in UTF-8
struct KeyInput {
const static size_t UTF8_ARR_SIZE = 5;
eAGSKeyCode Key = eAGSKeyCodeNone; // actual key code
eAGSKeyCode CompatKey = eAGSKeyCodeNone; // old-style key code, combined with mods
int Mod = 0; // key modifiers
int UChar = 0; // full character value (supports unicode)
char Text[UTF8_ARR_SIZE]{}; // character in a string format
KeyInput() = default;
};
// AGS own mouse button codes;
// These correspond to MouseButton enum in script and plugin API (sans special values)
enum eAGSMouseButton
{
kMouseNone = 0,
kMouseLeft = 1,
kMouseRight = 2,
kMouseMiddle = 3,
kNumMouseButtons
};
// Tells if the AGS keycode refers to the modifier key (ctrl, alt, etc)
inline bool IsAGSModKey(eAGSKeyCode keycode) {
return (keycode >= eAGSKeyCodeLShift && keycode <= eAGSKeyCodeLAlt) || keycode == eAGSKeyCodeRAlt;
}
// Tells if the AGS keycode refers to the service key (modifier, PrintScreen and similar);
// this lets distinct keys that normally should not affect the game
inline bool IsAGSServiceKey(eAGSKeyCode keycode) {
return keycode >= eAGSKeyCode_FirstServiceKey;
}
// Converts eAGSKeyCode to script API code, for "on_key_press" and similar callbacks
eAGSKeyCode AGSKeyToScriptKey(eAGSKeyCode keycode);
// Converts eAGSKeyCode to ASCII text representation with the range check; returns 0 on failure
// Not unicode compatible.
char AGSKeyToText(eAGSKeyCode keycode);
} // namespace AGS3
#endif

View File

@@ -0,0 +1,80 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/ac/mouse_cursor.h"
#include "ags/shared/util/stream.h"
#include "ags/shared/util/string_utils.h"
#include "common/util.h"
namespace AGS3 {
using namespace AGS::Shared;
void MouseCursor::clear() {
pic = 0;
hotx = hoty = 0;
view = -1;
name.Empty();
flags = 0;
}
void MouseCursor::ReadFromFile(Stream *in) {
pic = in->ReadInt32();
hotx = in->ReadInt16();
hoty = in->ReadInt16();
view = in->ReadInt16();
StrUtil::ReadCStrCount(legacy_name, in, LEGACY_MAX_CURSOR_NAME_LENGTH);
flags = in->ReadInt8();
in->Seek(3); // alignment padding to int32
name = legacy_name;
}
void MouseCursor::WriteToFile(Stream *out) {
out->WriteInt32(pic);
out->WriteInt16(hotx);
out->WriteInt16(hoty);
out->WriteInt16(view);
out->Write(legacy_name, LEGACY_MAX_CURSOR_NAME_LENGTH);
out->WriteInt8(flags);
out->WriteByteCount(0, 3); // alignment padding to int32
}
void MouseCursor::ReadFromSavegame(Stream *in, int cmp_ver) {
pic = in->ReadInt32();
hotx = static_cast<int16_t>(in->ReadInt32());
hoty = static_cast<int16_t>(in->ReadInt32());
view = static_cast<int16_t>(in->ReadInt32());
flags = static_cast<int8_t>(in->ReadInt32());
if (cmp_ver >= kCursorSvgVersion_36016)
animdelay = in->ReadInt32();
}
void MouseCursor::WriteToSavegame(Stream *out) const {
out->WriteInt32(pic);
out->WriteInt32(hotx);
out->WriteInt32(hoty);
out->WriteInt32(view);
out->WriteInt32(flags);
out->WriteInt32(animdelay);
}
} // namespace AGS3

View File

@@ -0,0 +1,76 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_AC_MOUSE_CURSOR_H
#define AGS_SHARED_AC_MOUSE_CURSOR_H
#include "ags/shared/core/types.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class Stream;
} // namespace Shared
} // namespace AGS
using namespace AGS; // FIXME later
#define MCF_ANIMMOVE 1
#define MCF_DISABLED 2
#define MCF_STANDARD 4
#define MCF_HOTSPOT 8 // only animate when over hotspot
#define LEGACY_MAX_CURSOR_NAME_LENGTH 10
enum CursorSvgVersion {
kCursorSvgVersion_Initial = 0,
kCursorSvgVersion_36016 = 1, // animation delay
};
// IMPORTANT: exposed to plugin API as AGSCursor!
// do not change topmost fields, unless planning breaking compatibility.
struct MouseCursor {
int pic = 0;
short hotx = 0, hoty = 0;
short view = -1;
// This is a deprecated name field, but must stay here for compatibility
// with the plugin API (unless the plugin interface is reworked)
char legacy_name[LEGACY_MAX_CURSOR_NAME_LENGTH]{};
char flags = 0;
// Following fields are not part of the plugin API
Shared::String name;
int animdelay = 5;
MouseCursor() {}
void clear();
void ReadFromFile(Shared::Stream *in);
void WriteToFile(Shared::Stream *out);
void ReadFromSavegame(Shared::Stream *in, int cmp_ver);
void WriteToSavegame(Shared::Stream *out) const;
};
} // namespace AGS3
#endif

View File

@@ -0,0 +1,88 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_AC_OLD_GAME_SETUP_STRUCT_H
#define AGS_SHARED_AC_OLD_GAME_SETUP_STRUCT_H
#if defined (OBSOLETE)
#include "ags/shared/ac/character_info.h" // OldCharacterInfo, CharacterInfo
#include "ags/shared/ac/event_block.h" // EventBlock
#include "ags/shared/ac/interface_element.h" // InterfaceElement
#include "ags/shared/ac/inventory_item_info.h" // InventoryItemInfo
#include "ags/shared/ac/mouse_cursor.h" // MouseCursor
#include "ags/shared/ac/words_dictionary.h" // WordsDictionary
#include "ags/shared/script/cc_script.h" // ccScript
namespace AGS3 {
struct OriGameSetupStruct {
char gamename[30];
int8 options[20];
unsigned char paluses[256];
RGB defpal[256];
InterfaceElement iface[10];
int numiface;
int numviews;
MouseCursor mcurs[10];
char *globalscript;
int numcharacters;
OldCharacterInfo *chars;
#if defined (OBSOLETE)
EventBlock __charcond[50];
EventBlock __invcond[100];
#endif
ccScript *compiled_script;
int playercharacter;
unsigned char __old_spriteflags[2100];
int totalscore;
short numinvitems;
InventoryItemInfo invinfo[100];
int numdialog, numdlgmessage;
int numfonts;
int color_depth; // in bytes per pixel (ie. 1 or 2)
int target_win;
int dialog_bullet; // 0 for none, otherwise slot num of bullet point
short hotdot, hotdotouter; // inv cursor hotspot dot
int uniqueid; // random key identifying the game
int reserved[2];
short numlang;
char langcodes[MAXLANGUAGE][3];
char *messages[MAXGLOBALMES];
};
struct OriGameSetupStruct2 : public OriGameSetupStruct {
unsigned char fontflags[10];
int8 fontoutline[10];
int numgui;
WordsDictionary *dict;
int reserved2[8];
};
struct OldGameSetupStruct : public OriGameSetupStruct2 {
unsigned char spriteflags[LEGACY_MAX_SPRITES_V25];
};
} // namespace AGS3
#endif
#endif

View File

@@ -0,0 +1,463 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// sprite caching system
//
//=============================================================================
#include "common/system.h"
#include "ags/shared/core/platform.h"
#include "ags/shared/util/stream.h"
#include "common/std/algorithm.h"
#include "ags/shared/ac/sprite_cache.h"
#include "ags/shared/ac/game_struct_defines.h"
#include "ags/shared/debugging/out.h"
#include "ags/shared/gfx/bitmap.h"
#include "ags/globals.h"
namespace AGS3 {
using namespace AGS::Shared;
// Tells that the sprite is found in the game resources.
#define SPRCACHEFLAG_ISASSET 0x01
// Tells that the sprite is assigned externally and cannot be autodisposed.
#define SPRCACHEFLAG_EXTERNAL 0x02
// Tells that the asset sprite failed to load
#define SPRCACHEFLAG_ERROR 0x04
// Locked sprites are ones that should not be freed when out of cache space.
#define SPRCACHEFLAG_LOCKED 0x08
// High-verbosity sprite cache log
#if DEBUG_SPRITECACHE
#define SprCacheLog(...) Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Debug, __VA_ARGS__)
#else
#define SprCacheLog(...)
#endif
namespace AGS {
namespace Shared {
SpriteCache::SpriteCache(std::vector<SpriteInfo> &sprInfos, const Callbacks &callbacks)
: _sprInfos(sprInfos), _maxCacheSize(DEFAULTCACHESIZE_KB * 1024u),
_cacheSize(0u), _lockedSize(0u) {
_callbacks.AdjustSize = (callbacks.AdjustSize) ? callbacks.AdjustSize : DummyAdjustSize;
_callbacks.InitSprite = (callbacks.InitSprite) ? callbacks.InitSprite : DummyInitSprite;
_callbacks.PostInitSprite = (callbacks.PostInitSprite) ? callbacks.PostInitSprite : DummyPostInitSprite;
_callbacks.PrewriteSprite = (callbacks.PrewriteSprite) ? callbacks.PrewriteSprite : DummyPrewriteSprite;
// Generate a placeholder sprite: 1x1 transparent bitmap
_placeholder.reset(BitmapHelper::CreateTransparentBitmap(1, 1, 8));
}
size_t SpriteCache::GetCacheSize() const {
return _cacheSize;
}
size_t SpriteCache::GetLockedSize() const {
return _lockedSize;
}
size_t SpriteCache::GetMaxCacheSize() const {
return _maxCacheSize;
}
size_t SpriteCache::GetSpriteSlotCount() const {
return _spriteData.size();
}
void SpriteCache::SetMaxCacheSize(size_t size) {
FreeMem(size);
_maxCacheSize = size;
}
bool SpriteCache::HasFreeSlots() const {
return !((_spriteData.size() == SIZE_MAX) || (_spriteData.size() > MAX_SPRITE_INDEX));
}
bool SpriteCache::IsAssetSprite(sprkey_t index) const {
return index >= 0 && (size_t)index < _spriteData.size() && // in the valid range
_spriteData[index].IsAssetSprite(); // found in the game resources
}
void SpriteCache::Reset() {
_file.Close();
_spriteData.clear();
_mru.clear();
_cacheSize = 0;
_lockedSize = 0;
}
bool SpriteCache::SetSprite(sprkey_t index, std::unique_ptr<Bitmap> image, int flags) {
if (index < 0 || EnlargeTo(index) != index) {
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SetSprite: unable to use index %d", index);
return false;
}
if (!image || image->GetSize().IsNull() || image->GetColorDepth() <= 0) {
DeleteSprite(index); // free previous item in this slot anyway
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SetSprite: attempt to assign an invalid bitmap to index %d", index);
return false;
}
const int spf_flags = flags
| (SPF_HICOLOR * image->GetColorDepth() > 8)
| (SPF_TRUECOLOR * image->GetColorDepth() > 16);
_sprInfos[index] = SpriteInfo(image->GetWidth(), image->GetHeight(), spf_flags);
// Assign sprite with 0 size, as it will not be included into the cache size
_spriteData[index] = SpriteData(image.release(), 0, SPRCACHEFLAG_EXTERNAL | SPRCACHEFLAG_LOCKED);
SprCacheLog("SetSprite: (external) %d", index);
return true;
}
void SpriteCache::SetEmptySprite(sprkey_t index, bool as_asset) {
if (index < 0 || EnlargeTo(index) != index) {
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SetEmptySprite: unable to use index %d", index);
return;
}
if (as_asset)
_spriteData[index].Flags = SPRCACHEFLAG_ISASSET;
RemapSpriteToPlaceholder(index);
}
Bitmap *SpriteCache::RemoveSprite(sprkey_t index) {
if (index < 0 || (size_t)index >= _spriteData.size())
return nullptr;
Bitmap *image = _spriteData[index].Image.release();
InitNullSprite(index);
SprCacheLog("RemoveSprite: %d", index);
return image;
}
void SpriteCache::DeleteSprite(sprkey_t index) {
assert(index >= 0); // out of positive range indexes are valid to fail
if (index < 0 || (size_t)index >= _spriteData.size())
return;
InitNullSprite(index);
SprCacheLog("RemoveAndDispose: %d", index);
}
sprkey_t SpriteCache::EnlargeTo(sprkey_t topmost) {
if (topmost < 0 || topmost > MAX_SPRITE_INDEX)
return -1;
if ((size_t)topmost < _spriteData.size())
return topmost;
size_t newsize = topmost + 1;
_sprInfos.resize(newsize);
_spriteData.resize(newsize);
return topmost;
}
sprkey_t SpriteCache::GetFreeIndex() {
// FIXME: inefficient if large number of sprites were created in game;
// use "available ids" stack, see managed pool for an example;
// NOTE: this is shared with the Editor, which means we cannot rely on the
// number of "static" sprites and search for slots after... this may be
// resolved by splitting SpriteCache class further on "cache builder" and
// "runtime cache".
for (size_t i = MIN_SPRITE_INDEX; i < _spriteData.size(); ++i) {
// slot empty
if (!DoesSpriteExist(i)) {
_sprInfos[i] = SpriteInfo();
_spriteData[i] = SpriteData();
return i;
}
}
// enlarge the sprite bank to find a free slot and return the first new free slot
return EnlargeTo(_spriteData.size());
}
bool SpriteCache::SpriteData::DoesSpriteExist() const {
return (Image != nullptr) || // HAS loaded bitmap
((Flags & SPRCACHEFLAG_ISASSET) != 0); // OR found in the game resources
}
bool SpriteCache::SpriteData::IsAssetSprite() const {
return (Flags & SPRCACHEFLAG_ISASSET) != 0;
}
bool SpriteCache::SpriteData::IsError() const {
return (Flags & SPRCACHEFLAG_ERROR) != 0;
}
bool SpriteCache::SpriteData::IsExternalSprite() const {
return (Flags & SPRCACHEFLAG_EXTERNAL) != 0;
}
bool SpriteCache::SpriteData::IsLocked() const {
return (Flags & SPRCACHEFLAG_LOCKED) != 0;
}
bool SpriteCache::DoesSpriteExist(sprkey_t index) const {
return (index >= 0 && (size_t)index < _spriteData.size()) && // in the valid range
_spriteData[index].IsValid(); // has assigned sprite
}
Size SpriteCache::GetSpriteResolution(sprkey_t index) const {
return DoesSpriteExist(index) ? _sprInfos[index].GetResolution() : Size();
}
Bitmap *SpriteCache::operator[](sprkey_t index) {
// invalid sprite slot
if (!DoesSpriteExist(index) || _spriteData[index].IsError())
return _placeholder.get();
// Externally added sprite or locked sprite, don't put it into MRU list
if (_spriteData[index].IsExternalSprite() || _spriteData[index].IsLocked())
return _spriteData[index].Image.get();
// Either use ready image, or load one from assets
if (_spriteData[index].Image) {
// Move to the beginning of the MRU list
_mru.splice(_mru.begin(), _mru, _spriteData[index].MruIt);
return _spriteData[index].Image.get();
} else {
// Sprite exists in file but is not in mem, load it and add to MRU list
if (LoadSprite(index)) {
_spriteData[index].MruIt = _mru.insert(_mru.begin(), index);
return _spriteData[index].Image.get();
}
}
return _placeholder.get();
}
void SpriteCache::FreeMem(size_t space) {
for (int tries = 0; (_mru.size() > 0) && (_cacheSize >= (_maxCacheSize - space)); ++tries) {
DisposeOldest();
if (tries > 1000) { // ???
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "RUNTIME CACHE ERROR: STUCK IN FREE_UP_MEM; RESETTING CACHE");
DisposeAllFreeCached();
}
}
}
void SpriteCache::DisposeOldest() {
assert(_mru.size() > 0);
if (_mru.size() == 0)
return;
auto it = std::prev(_mru.end());
const auto sprnum = *it;
// Safety check: must be a sprite from resources
// TODO: compare with latest upstream
// Commented out the assertion, since it triggers for sprites that are in the list but remapped to the placeholder (sprite 0)
// Whispers of a Machine is affected by this issue (see TRAC #14730)
// assert(_spriteData[sprnum].IsAssetSprite());
if (!_spriteData[sprnum].IsAssetSprite()) {
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SpriteCache::DisposeOldest: in MRU list sprite %d is external or does not exist", sprnum);
_mru.erase(it);
// std::list::erase() invalidates iterators to the erased item.
// But our implementation does not.
_spriteData[sprnum].MruIt._node = nullptr;
return;
}
// Delete the image, unless is locked
// NOTE: locked sprites may still occur in MRU list
if (!_spriteData[sprnum].IsLocked()) {
_cacheSize -= _spriteData[sprnum].Size;
_spriteData[sprnum].Image.reset();
SprCacheLog("DisposeOldest: disposed %d, size now %d KB", sprnum, _cacheSize / 1024);
}
// Remove from the mru list
_mru.erase(it);
// std::list::erase() invalidates iterators to the erased item.
// But our implementation does not.
_spriteData[sprnum].MruIt._node = nullptr;
}
void SpriteCache::DisposeCached(sprkey_t index) {
if (IsAssetSprite(index)) {
_spriteData[index].Flags &= ~SPRCACHEFLAG_LOCKED;
_spriteData[index].Image.reset();
}
_cacheSize = _lockedSize;
}
void SpriteCache::DisposeAllFreeCached() {
for (size_t i = 0; i < _spriteData.size(); ++i) {
if (!_spriteData[i].IsLocked() && // not locked
_spriteData[i].IsAssetSprite()) // sprite from game resource
{
_spriteData[i].Image.reset();
}
}
_cacheSize = _lockedSize;
_mru.clear();
}
void SpriteCache::PrecacheSprite(sprkey_t index) {
if (index < 0 || (size_t)index >= _spriteData.size())
return;
if (!_spriteData[index].IsAssetSprite())
return; // cannot precache a non-asset sprite
size_t size = 0;
if (_spriteData[index].Image == nullptr) {
size = LoadSprite(index);
} else if (!_spriteData[index].IsLocked()) {
size = _spriteData[index].Size;
// Remove locked sprite from the MRU list
_mru.erase(_spriteData[index].MruIt);
// std::list::erase() invalidates iterators to the erased item.
// But our implementation does not.
_spriteData[index].MruIt._node = nullptr;
}
// make sure locked sprites can't fill the cache
_maxCacheSize += size;
_lockedSize += size;
_spriteData[index].Flags |= SPRCACHEFLAG_LOCKED;
SprCacheLog("Precached %d", index);
}
void SpriteCache::LockSprite(sprkey_t index) {
assert(index >= 0); // out of positive range indexes are valid to fail
if (index < 0 || (size_t)index >= _spriteData.size())
return;
if (!_spriteData[index].IsAssetSprite())
return; // cannot lock a non-asset sprite
if (_spriteData[index].DoesSpriteExist()) {
_spriteData[index].Flags |= SPRCACHEFLAG_LOCKED;
} else {
LoadSprite(index, true);
}
SprCacheLog("Locked %d", index);
}
void SpriteCache::UnlockSprite(sprkey_t index) {
assert(index >= 0); // out of positive range indexes are valid to fail
if (index < 0 || (size_t)index >= _spriteData.size())
return;
if (!_spriteData[index].IsAssetSprite() ||
!_spriteData[index].IsLocked())
return; // cannot unlock a non-asset sprite, or non-locked sprite
_spriteData[index].Flags &= ~SPRCACHEFLAG_LOCKED;
SprCacheLog("Unlocked %d", index);
}
size_t SpriteCache::LoadSprite(sprkey_t index, bool lock) {
assert((index >= 0) && ((size_t)index < _spriteData.size()));
if (index < 0 || (size_t)index >= _spriteData.size())
return 0;
assert((_spriteData[index].Flags & SPRCACHEFLAG_ISASSET) != 0);
Bitmap *image;
HError err = _file.LoadSprite(index, image);
if (!image) {
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Warn,
"LoadSprite: failed to load sprite %d:\n%s\n - remapping to placeholder", index,
err ? "Sprite does not exist." : err->FullMessage().GetCStr());
RemapSpriteToPlaceholder(index);
return 0;
}
// Let the external user convert this sprite's image for their needs
image = _callbacks.InitSprite(index, image, _sprInfos[index].Flags);
if (!image) {
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Warn,
"LoadSprite: failed to initialize sprite %d, remapping to placeholder", index);
RemapSpriteToPlaceholder(index);
return 0;
}
// save the stored sprite info
_sprInfos[index].Width = image->GetWidth();
_sprInfos[index].Height = image->GetHeight();
// Clear up space before adding to cache
const size_t size = image->GetWidth() * image->GetHeight() * image->GetBPP();
FreeMem(size);
// Add to the cache, lock if requested or if it's sprite 0
const bool should_lock = lock || (index == 0);
_spriteData[index] = SpriteData(image, size, SPRCACHEFLAG_ISASSET);
_spriteData[index].Flags |= (SPRCACHEFLAG_LOCKED * should_lock);
_cacheSize += size;
SprCacheLog("Loaded %d, size now %zu KB", index, _cacheSize / 1024);
// Let the external user to react to the new sprite;
// note that this callback is allowed to modify the sprite's pixels,
// but not its size or flags.
_callbacks.PostInitSprite(index);
return size;
}
void SpriteCache::RemapSpriteToPlaceholder(sprkey_t index) {
assert((index > 0) && ((size_t)index < _spriteData.size()));
_sprInfos[index] = SpriteInfo(_placeholder->GetWidth(), _placeholder->GetHeight(), _placeholder->GetColorDepth());
_spriteData[index].Flags |= SPRCACHEFLAG_ERROR;
SprCacheLog("RemapSpriteToPlaceholder: %d", index);
}
void SpriteCache::InitNullSprite(sprkey_t index) {
assert(index >= 0);
_sprInfos[index] = SpriteInfo();
_spriteData[index] = SpriteData();
}
int SpriteCache::SaveToFile(const String &filename, int store_flags, SpriteCompression compress, SpriteFileIndex &index) {
std::vector<std::pair<bool, Bitmap *>> sprites;
for (size_t i = 0; i < _spriteData.size(); ++i) {
_callbacks.PrewriteSprite(_spriteData[i].Image.get());
sprites.push_back(std::make_pair(DoesSpriteExist(i), _spriteData[i].Image.get()));
}
return SaveSpriteFile(filename, sprites, &_file, store_flags, compress, index);
}
HError SpriteCache::InitFile(const String &filename, const String &sprindex_filename) {
Reset();
std::vector<Size> metrics;
HError err = _file.OpenFile(filename, sprindex_filename, metrics);
if (!err)
return err;
// Initialize sprite infos
size_t newsize = metrics.size();
_sprInfos.resize(newsize);
_spriteData.resize(newsize);
_mru.clear();
for (size_t i = 0; i < metrics.size(); ++i) {
if (!metrics[i].IsNull()) {
// Existing sprite
_spriteData[i].Flags = SPRCACHEFLAG_ISASSET;
Size newsz = _callbacks.AdjustSize(Size(metrics[i].Width, metrics[i].Height), _sprInfos[i].Flags);
_sprInfos[i].Width = newsz.Width;
_sprInfos[i].Height = newsz.Height;
} else {
// Mark as empty slot
InitNullSprite(i);
}
}
return HError::None();
}
void SpriteCache::DetachFile() {
_file.Close();
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,254 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//
// Sprite caching system.
//
// SpriteFile handles sprite serialization and streaming.
// SpriteCache provides bitmaps by demand; it uses SpriteFile to load sprites
// and does MRU (most-recent-use) caching.
//
// TODO: store sprite data in a specialized container type that is optimized
// for having most keys allocated in large continious sequences by default.
//
// Only for the reference: one of the ideas is for container to have a table
// of arrays of fixed size internally. When getting an item the hash would be
// first divided on array size to find the array the item resides in, then the
// item is taken from item from slot index = (hash - arrsize * arrindex).
// TODO: find out if there is already a hash table kind that follows similar
// principle.
//
//=============================================================================
#ifndef AGS_SHARED_AC_SPRITE_CACHE_H
#define AGS_SHARED_AC_SPRITE_CACHE_H
#include "common/std/memory.h"
#include "common/std/vector.h"
#include "common/std/list.h"
#include "ags/shared/ac/sprite_file.h"
#include "ags/shared/core/platform.h"
#include "ags/shared/gfx/bitmap.h"
#include "ags/shared/util/error.h"
#include "ags/shared/util/geometry.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class String;
class Stream;
class Bitmap;
} // namespace AGS3
} // namespace AGS
using namespace AGS; // FIXME later
typedef AGS::Shared::HError HAGSError;
struct SpriteInfo;
// Max size of the sprite cache, in bytes
#if AGS_PLATFORM_OS_ANDROID || AGS_PLATFORM_OS_IOS
#define DEFAULTCACHESIZE_KB (32 * 1024)
#else
#define DEFAULTCACHESIZE_KB (128 * 1024)
#endif
struct SpriteInfo;
namespace AGS {
namespace Shared {
class SpriteCache {
public:
static const sprkey_t MIN_SPRITE_INDEX = 1; // 0 is reserved for "empty sprite"
static const sprkey_t MAX_SPRITE_INDEX = INT32_MAX - 1;
static const size_t MAX_SPRITE_SLOTS = INT32_MAX;
typedef Size (*PfnAdjustSpriteSize)(const Size &size, const uint32_t sprite_flags);
typedef Bitmap *(*PfnInitSprite)(sprkey_t index, Bitmap *image, uint32_t &sprite_flags);
typedef void (*PfnPostInitSprite)(sprkey_t index);
typedef void (*PfnPrewriteSprite)(Bitmap *image);
struct Callbacks {
PfnAdjustSpriteSize AdjustSize;
PfnInitSprite InitSprite;
PfnPostInitSprite PostInitSprite;
PfnPrewriteSprite PrewriteSprite;
};
SpriteCache(std::vector<SpriteInfo> &sprInfos, const Callbacks &callbacks);
~SpriteCache() = default;
// Loads sprite reference information and inits sprite stream
HError InitFile(const String &filename, const String &sprindex_filename);
// Saves current cache contents to the file
int SaveToFile(const String &filename, int store_flags, SpriteCompression compress, SpriteFileIndex &index);
// Closes an active sprite file stream
void DetachFile();
inline int GetStoreFlags() const {
return _file.GetStoreFlags();
}
inline SpriteCompression GetSpriteCompression() const {
return _file.GetSpriteCompression();
}
// Tells if there is a sprite registered for the given index;
// this includes sprites that were explicitly assigned but failed to init and were remapped
bool DoesSpriteExist(sprkey_t index) const;
// Returns sprite's resolution; or empty Size if sprite does not exist
Size GetSpriteResolution(sprkey_t index) const;
// Makes sure sprite cache has allocated slots for all sprites up to the given inclusive limit;
// returns requested index on success, or -1 on failure.
sprkey_t EnlargeTo(sprkey_t topmost);
// Finds a free slot index, if all slots are occupied enlarges sprite bank; returns index
sprkey_t GetFreeIndex();
// Returns current size of the cache, in bytes; this includes locked size too!
size_t GetCacheSize() const;
// Gets the total size of the locked sprites, in bytes
size_t GetLockedSize() const;
// Returns maximal size limit of the cache, in bytes; this includes locked size too!
size_t GetMaxCacheSize() const;
// Returns number of sprite slots in the bank (this includes both actual sprites and free slots)
size_t GetSpriteSlotCount() const;
// Tells if the sprite storage still has unoccupied slots to put new sprites in
bool HasFreeSlots() const;
// Tells if the given slot is reserved for the asset sprite, that is a "static"
// sprite cached from the game assets
bool IsAssetSprite(sprkey_t index) const;
// Loads sprite using SpriteFile if such index is known,
// frees the space if cache size reaches the limit
void PrecacheSprite(sprkey_t index);
// Locks sprite, preventing it from getting removed by the normal cache limit.
// If this is a registered sprite from the game assets, then loads it first.
// If this is a sprite with SPRCACHEFLAG_EXTERNAL flag, then does nothing,
// as these are always "locked".
// If such sprite does not exist, then fails silently.
void LockSprite(sprkey_t index);
// Unlocks sprite, putting it back into the cache logic,
// where it counts towards normal limit may be deleted to free space.
// NOTE: sprites with SPRCACHEFLAG_EXTERNAL flag cannot be unlocked,
// only explicitly removed.
// If such sprite was not present in memory, then fails silently.
void UnlockSprite(sprkey_t index);
// Unregisters sprite from the bank and returns the bitmap
Bitmap *RemoveSprite(sprkey_t index);
// Deletes particular sprite, marks slot as unused
void DeleteSprite(sprkey_t index);
// Deletes a loaded asset image (non-external) from memory, ignoring its
// locked status; this keeps an auxiliary sprite information intact,
// so that the same sprite can be cached back later.
void DisposeCached(sprkey_t index);
// Deletes all free cached asset images (non-locked, non-external)
// from memory; this keeps all the auxiliary sprite information intact,
// so that the same sprite(s) can be cached back later.
void DisposeAllFreeCached();
// Deletes all data and resets cache to the clear state
void Reset();
// Assigns new sprite for the given index; this sprite won't be auto disposed.
// *Deletes* the previous sprite if one was found at the same index.
// "flags" are SPF_* constants that define sprite's behavior in game.
bool SetSprite(sprkey_t index, std::unique_ptr<Bitmap> image, int flags = 0);
// Assigns new dummy for the given index, silently remapping it to placeholder;
// optionally marks it as an asset placeholder.
// *Deletes* the previous sprite if one was found at the same index.
void SetEmptySprite(sprkey_t index, bool as_asset);
// Sets max cache size in bytes
void SetMaxCacheSize(size_t size);
// Loads (if it's not in cache yet) and returns bitmap by the sprite index
Bitmap *operator[](sprkey_t index);
private:
// Load sprite from game resource
size_t LoadSprite(sprkey_t index, bool lock = false);
// Remap the given index to the placeholder
void RemapSpriteToPlaceholder(sprkey_t index);
// Delete the oldest (least recently used) image in cache
void DisposeOldest();
// Keep disposing oldest elements until cache has at least the given free space
void FreeMem(size_t space);
// Initialize the empty sprite slot
void InitNullSprite(sprkey_t index);
//
// Dummy no-op variants for callbacks
//
static Size DummyAdjustSize(const Size &size, const uint32_t) { return size; }
static Bitmap *DummyInitSprite(sprkey_t, Bitmap *image, uint32_t &) { return image; }
static void DummyPostInitSprite(sprkey_t) { /* do nothing */ }
static void DummyPrewriteSprite(Bitmap *) { /* do nothing */ }
// Information required for the sprite streaming
struct SpriteData {
size_t Size = 0; // to track cache size, 0 = means don't track
uint32_t Flags = 0; // SPRCACHEFLAG* flags
std::unique_ptr<Bitmap> Image; // actual bitmap
// MRU list reference
std::list<sprkey_t>::iterator MruIt;
SpriteData() = default;
SpriteData(SpriteData &&other) = default;
SpriteData(Bitmap *image, size_t size, uint32_t flags) : Size(size), Flags(flags), Image(image) {}
SpriteData &operator=(SpriteData &&other) = default;
// Tells if this slot has a valid sprite assigned (not empty slot)
bool IsValid() const { return Flags != 0u; }
// Tells if there actually is a registered sprite in this slot
bool DoesSpriteExist() const;
// Tells if there's a game resource corresponding to this slot
bool IsAssetSprite() const;
// Tells if a sprite failed to load from assets, and should not be used
bool IsError() const;
// Tells if sprite was added externally, not loaded from game resources
bool IsExternalSprite() const;
// Tells if sprite is locked and should not be disposed by cache logic
bool IsLocked() const;
};
// Provided map of sprite infos, to fill in loaded sprite properties
std::vector<SpriteInfo> &_sprInfos;
// Array of sprite references
std::vector<SpriteData> _spriteData;
// Placeholder sprite, returned from operator[] for a non-existing sprite
std::unique_ptr<Bitmap> _placeholder;
Callbacks _callbacks;
SpriteFile _file;
size_t _maxCacheSize; // cache size limit
size_t _lockedSize; // size in bytes of currently locked images
size_t _cacheSize; // size in bytes of currently cached images
// MRU list: the way to track which sprites were used recently.
// When clearing up space for new sprites, cache first deletes the sprites
// that were last time used long ago.
std::list<sprkey_t> _mru;
};
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,746 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/ac/sprite_file.h"
#include "common/std/algorithm.h"
#include "ags/shared/core/asset_manager.h"
#include "ags/shared/gfx/bitmap.h"
#include "ags/shared/util/compress.h"
#include "ags/shared/util/file.h"
#include "ags/shared/util/memory_stream.h"
#include "ags/shared/util/stream.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
static const char *spriteFileSig = " Sprite File ";
static const char *spindexid = "SPRINDEX";
// TODO: should not be part of SpriteFile, but rather some asset management class?
const char *SpriteFile::DefaultSpriteFileName = "acsprset.spr";
const char *SpriteFile::DefaultSpriteIndexName = "sprindex.dat";
// Image buffer pointer, a helper struct that eases switching
// between intermediate buffers when loading, saving or converting an image.
template <typename T> struct ImBufferPtrT {
T Buf = nullptr;
size_t Size = 0;
int BPP = 1; // byte per pixel
ImBufferPtrT() = default;
ImBufferPtrT(T buf, size_t sz, int bpp) : Buf(buf), Size(sz), BPP(bpp) {
}
};
typedef ImBufferPtrT<uint8_t *> ImBufferPtr;
typedef ImBufferPtrT<const uint8_t *> ImBufferCPtr;
// Finds the given color's index in the palette, or returns SIZE_MAX if such color is not there
static size_t lookup_palette(uint32_t col, uint32_t palette[256], uint32_t ncols) {
for (size_t i = 0; i < ncols; ++i)
if (palette[i] == col) return i;
return SIZE_MAX;
}
// Converts a 16/32-bit image into the indexed 8-bit pixel data with palette;
// NOTE: the palette will contain colors in the same format as the source image.
// only succeeds if the total number of colors used in the image is < 257.
static bool CreateIndexedBitmap(const Bitmap *image, std::vector<uint8_t> &dst_data,
uint32_t palette[256], uint32_t &pal_count) {
const int src_bpp = image->GetBPP();
if (src_bpp < 2) { assert(0); return false; }
const size_t src_size = image->GetWidth() * image->GetHeight() * image->GetBPP();
const size_t dst_size = image->GetWidth() * image->GetHeight();
dst_data.resize(dst_size);
const uint8_t *src = image->GetData(), *src_end = src + src_size;
uint8_t *dst = &dst_data[0], *dst_end = dst + dst_size;
pal_count = 0;
for (; src < src_end && dst < dst_end; src += src_bpp) {
uint32_t col = 0;
size_t pal_n = 0;
switch (src_bpp) {
case 2:
col = *((const uint16_t *)src);
pal_n = lookup_palette(col, palette, pal_count);
break;
case 4:
col = *((const uint32_t *)src);
pal_n = lookup_palette(col, palette, pal_count);
break;
default: assert(0); return false;
}
if (pal_n == SIZE_MAX) {
if (pal_count == 256) return false;
pal_n = pal_count;
palette[pal_count++] = col;
}
*(dst++) = (uint8_t)pal_n;
}
return true;
}
// Unpacks an indexed image's pixel data into the 16/32-bit image;
// NOTE: the palette is expected to contain colors in the same format as the destination.
static void UnpackIndexedBitmap(Bitmap *image, const uint8_t *data, size_t data_size,
uint32_t *palette, uint32_t pal_count) {
assert(pal_count > 0);
if (pal_count == 0) return; // meaningless
const uint8_t bpp = image->GetBPP();
const size_t dst_size = image->GetWidth() * image->GetHeight() * image->GetBPP();
uint8_t *dst = image->GetDataForWriting(), *dst_end = dst + dst_size;
switch (bpp) {
case 2:
for (size_t p = 0; (p < data_size) && (dst < dst_end); ++p, dst += bpp) {
uint8_t index = data[p];
assert(index < pal_count);
uint32_t color = palette[(index < pal_count) ? index : 0];
*((uint16_t *)dst) = color;
}
break;
case 4:
for (size_t p = 0; (p < data_size) && (dst < dst_end); ++p, dst += bpp) {
uint8_t index = data[p];
assert(index < pal_count);
uint32_t color = palette[(index < pal_count) ? index : 0];
*((uint32_t *)dst) = color;
}
break;
default:
assert(0);
return;
}
}
static inline SpriteFormat PaletteFormatForBPP(int bpp) {
switch (bpp) {
case 1: return kSprFmt_PaletteRgb888;
case 2: return kSprFmt_PaletteRgb565;
case 4: return kSprFmt_PaletteArgb8888;
default: return kSprFmt_Undefined;
}
}
static inline uint8_t GetPaletteBPP(SpriteFormat fmt) {
switch (fmt) {
case kSprFmt_PaletteRgb888: return 3;
case kSprFmt_PaletteArgb8888: return 4;
case kSprFmt_PaletteRgb565: return 2;
default: return 0; // means no palette
}
}
SpriteFile::SpriteFile() {
_curPos = -2;
}
HError SpriteFile::OpenFile(const String &filename, const String &sprindex_filename,
std::vector<Size> &metrics) {
Close();
char buff[20];
soff_t spr_initial_offs = 0;
int spriteFileID = 0;
_stream.reset(_GP(AssetMgr)->OpenAsset(filename));
if (_stream == nullptr)
return new Error(String::FromFormat("Failed to open spriteset file '%s'.", filename.GetCStr()));
spr_initial_offs = _stream->GetPosition();
_version = (SpriteFileVersion)_stream->ReadInt16();
// read the "Sprite File" signature
_stream->ReadArray(&buff[0], 13, 1);
if (_version < kSprfVersion_Uncompressed || _version > kSprfVersion_Current) {
_stream.reset();
return new Error(String::FromFormat("Unsupported spriteset format (requested %d, supported %d - %d).", _version,
kSprfVersion_Uncompressed, kSprfVersion_Current));
}
// unknown version
buff[13] = 0;
if (strcmp(buff, spriteFileSig)) {
_stream.reset();
return new Error("Uknown spriteset format.");
}
_storeFlags = 0;
if (_version < kSprfVersion_Compressed) {
_compress = kSprCompress_None;
// skip the palette
_stream->Seek(256 * 3); // sizeof(RGB) * 256
} else if (_version == kSprfVersion_Compressed) {
_compress = kSprCompress_RLE;
} else if (_version >= kSprfVersion_Last32bit) {
_compress = (SpriteCompression)_stream->ReadInt8();
spriteFileID = _stream->ReadInt32();
}
sprkey_t topmost;
if (_version < kSprfVersion_HighSpriteLimit)
topmost = (uint16_t)_stream->ReadInt16();
else
topmost = _stream->ReadInt32();
if (_version < kSprfVersion_Uncompressed)
topmost = 200;
_spriteData.resize(topmost + 1);
metrics.resize(topmost + 1);
// Version 12+: read global store flags
if (_version >= kSprfVersion_StorageFormats) {
_storeFlags = _stream->ReadInt8();
_stream->ReadInt8(); // reserved
_stream->ReadInt8();
_stream->ReadInt8();
}
// if there is a sprite index file, use it
if (LoadSpriteIndexFile(sprindex_filename, spriteFileID,
spr_initial_offs, topmost, metrics)) {
// Succeeded
return HError::None();
}
// Failed, index file is invalid; index sprites manually
return RebuildSpriteIndex(_stream.get(), topmost, metrics);
}
void SpriteFile::Close() {
_stream.reset();
_spriteData.clear();
_version = kSprfVersion_Undefined;
_storeFlags = 0;
_compress = kSprCompress_None;
_curPos = -2;
}
int SpriteFile::GetStoreFlags() const {
return _storeFlags;
}
SpriteCompression SpriteFile::GetSpriteCompression() const {
return _compress;
}
sprkey_t SpriteFile::GetTopmostSprite() const {
return (sprkey_t)_spriteData.size() - 1;
}
bool SpriteFile::LoadSpriteIndexFile(const String &filename, int expectedFileID,
soff_t spr_initial_offs, sprkey_t topmost, std::vector<Size> &metrics) {
Stream *fidx = _GP(AssetMgr)->OpenAsset(filename);
if (fidx == nullptr) {
return false;
}
char buffer[9];
// check "SPRINDEX" id
fidx->ReadArray(&buffer[0], strlen(spindexid), 1);
buffer[8] = 0;
if (strcmp(buffer, spindexid)) {
delete fidx;
return false;
}
// check version
SpriteIndexFileVersion vers = (SpriteIndexFileVersion)fidx->ReadInt32();
if (vers < kSpridxfVersion_Initial || vers > kSpridxfVersion_Current) {
delete fidx;
return false;
}
if (vers >= kSpridxfVersion_Last32bit) {
if (fidx->ReadInt32() != expectedFileID) {
delete fidx;
return false;
}
}
sprkey_t topmost_index = fidx->ReadInt32();
// end index+1 should be the same as num sprites
if (fidx->ReadInt32() != topmost_index + 1) {
delete fidx;
return false;
}
if (topmost_index != topmost) {
delete fidx;
return false;
}
sprkey_t numsprits = topmost_index + 1;
std::vector<int16_t> rspritewidths; rspritewidths.resize(numsprits);
std::vector<int16_t> rspriteheights; rspriteheights.resize(numsprits);
std::vector<soff_t> spriteoffs; spriteoffs.resize(numsprits);
fidx->ReadArrayOfInt16(&rspritewidths[0], numsprits);
fidx->ReadArrayOfInt16(&rspriteheights[0], numsprits);
if (vers <= kSpridxfVersion_Last32bit) {
for (sprkey_t i = 0; i < numsprits; ++i)
spriteoffs[i] = fidx->ReadInt32();
} else // large file support
{
fidx->ReadArrayOfInt64(&spriteoffs[0], numsprits);
}
delete fidx;
for (sprkey_t i = 0; i <= topmost_index; ++i) {
if (spriteoffs[i] != 0) {
_spriteData[i].Offset = spriteoffs[i] + spr_initial_offs;
metrics[i].Width = rspritewidths[i];
metrics[i].Height = rspriteheights[i];
}
}
return true;
}
static inline void ReadSprHeader(SpriteDatHeader &hdr, Stream *in,
const SpriteFileVersion ver, SpriteCompression gl_compress) {
int bpp = in->ReadInt8();
SpriteFormat sformat = (SpriteFormat)in->ReadInt8();
// note we MUST read first 2 * int8 before skipping rest
if (bpp == 0) {
hdr = SpriteDatHeader(); return;
} // empty slot
int pal_count = 0;
SpriteCompression compress = gl_compress;
if (ver >= kSprfVersion_StorageFormats) {
pal_count = (uint8_t)in->ReadInt8() + 1; // saved as (count - 1)
compress = (SpriteCompression)in->ReadInt8();
}
int w = in->ReadInt16();
int h = in->ReadInt16();
hdr = SpriteDatHeader(bpp, sformat, pal_count, compress, w, h);
}
HError SpriteFile::RebuildSpriteIndex(Stream *in, sprkey_t topmost,
std::vector<Size> &metrics) {
topmost = MIN(topmost, (sprkey_t)_spriteData.size() - 1);
for (sprkey_t i = 0; !in->EOS() && (i <= topmost); ++i) {
_spriteData[i].Offset = in->GetPosition();
SpriteDatHeader hdr;
ReadSprHeader(hdr, _stream.get(), _version, _compress);
if (hdr.BPP == 0) continue; // empty slot, this is normal
int pal_bpp = GetPaletteBPP(hdr.SFormat);
if (pal_bpp > 0) in->Seek(hdr.PalCount * pal_bpp); // skip palette
size_t data_sz =
((_version >= kSprfVersion_StorageFormats) || _compress != kSprCompress_None) ?
(uint32_t)in->ReadInt32() : hdr.Width * hdr.Height * hdr.BPP;
in->Seek(data_sz); // skip image data
metrics[i].Width = hdr.Width;
metrics[i].Height = hdr.Height;
}
return HError::None();
}
HError SpriteFile::LoadSprite(sprkey_t index, Shared::Bitmap *&sprite) {
sprite = nullptr;
if (index < 0 || (size_t)index >= _spriteData.size())
return new Error(String::FromFormat("LoadSprite: slot index %d out of bounds (%d - %d).",
index, 0, _spriteData.size() - 1));
if (_spriteData[index].Offset == 0)
return HError::None(); // sprite is not in file
SeekToSprite(index);
_curPos = -2; // mark undefined pos
SpriteDatHeader hdr;
ReadSprHeader(hdr, _stream.get(), _version, _compress);
if (hdr.BPP == 0) return HError::None(); // empty slot, this is normal
int bpp = hdr.BPP, w = hdr.Width, h = hdr.Height;
std::unique_ptr<Bitmap> image(BitmapHelper::CreateBitmap(w, h, bpp * 8));
if (image == nullptr) {
return new Error(String::FromFormat("LoadSprite: failed to allocate bitmap %d (%dx%d%d).",
index, w, h, bpp * 8));
}
ImBufferPtr im_data(image->GetDataForWriting(), w * h * bpp, bpp);
// (Optional) Handle storage options, reverse
std::vector<uint8_t> indexed_buf;
uint32_t palette[256];
uint32_t pal_bpp = GetPaletteBPP(hdr.SFormat);
if (pal_bpp > 0) { // read palette if format assumes one
switch (pal_bpp) {
case 2: for (uint32_t i = 0; i < hdr.PalCount; ++i) {
palette[i] = _stream->ReadInt16();
}
break;
case 4: for (uint32_t i = 0; i < hdr.PalCount; ++i) {
palette[i] = _stream->ReadInt32();
}
break;
default: assert(0); break;
}
indexed_buf.resize(w * h);
im_data = ImBufferPtr(&indexed_buf[0], indexed_buf.size(), 1);
}
// (Optional) Decompress the image data into the temp buffer
size_t in_data_size =
((_version >= kSprfVersion_StorageFormats) || _compress != kSprCompress_None) ?
(uint32_t)_stream->ReadInt32() : (w * h * bpp);
if (hdr.Compress != kSprCompress_None) {
// TODO: rewrite this to only make a choice once the SpriteFile is initialized
// and use either function ptr or a decompressing stream class object
if (in_data_size == 0) {
return new Error(String::FromFormat("LoadSprite: bad compressed data for sprite %d.", index));
}
bool result;
switch (hdr.Compress) {
case kSprCompress_RLE: result = rle_decompress(im_data.Buf, im_data.Size, im_data.BPP, _stream.get());
break;
case kSprCompress_LZW: result = lzw_decompress(im_data.Buf, im_data.Size, im_data.BPP, _stream.get(), in_data_size);
break;
case kSprCompress_Deflate: result = inflate_decompress(im_data.Buf, im_data.Size, im_data.BPP, _stream.get(), in_data_size);
break;
default: assert(!"Unsupported compression type!"); result = false; break;
}
// TODO: test that not more than data_size was read!
if (!result) {
return new Error(String::FromFormat("LoadSprite: failed to decompress pixel array for sprite %d.", index));
}
}
// Otherwise (no compression) read directly
else {
switch (im_data.BPP) {
case 1: _stream->Read(im_data.Buf, im_data.Size);
break;
case 2: _stream->ReadArrayOfInt16(
reinterpret_cast<int16_t *>(im_data.Buf), im_data.Size / sizeof(int16_t));
break;
case 4: _stream->ReadArrayOfInt32(
reinterpret_cast<int32_t *>(im_data.Buf), im_data.Size / sizeof(int32_t));
break;
default: assert(0); break;
}
}
// Finally revert storage options
if (pal_bpp > 0) {
UnpackIndexedBitmap(image.get(), im_data.Buf, im_data.Size, palette, hdr.PalCount);
}
sprite = image.release(); // FIXME: pass unique_ptr in this function
_curPos = index + 1; // mark correct pos
return HError::None();
}
HError SpriteFile::LoadRawData(sprkey_t index, SpriteDatHeader &hdr, std::vector<uint8_t> &data) {
hdr = SpriteDatHeader();
data.resize(0);
if (index < 0 || (size_t)index >= _spriteData.size())
return new Error(String::FromFormat("LoadSprite: slot index %d out of bounds (%d - %d).",
index, 0, _spriteData.size() - 1));
if (_spriteData[index].Offset == 0)
return HError::None(); // sprite is not in file
SeekToSprite(index);
_curPos = -2; // mark undefined pos
ReadSprHeader(hdr, _stream.get(), _version, _compress);
if (hdr.BPP == 0) return HError::None(); // empty slot, this is normal
size_t data_size = 0;
soff_t data_pos = _stream->GetPosition();
// Optional palette
size_t pal_size = hdr.PalCount * GetPaletteBPP(hdr.SFormat);
data_size += pal_size;
_stream->Seek(pal_size);
// Pixel data
if ((_version >= kSprfVersion_StorageFormats) || _compress != kSprCompress_None)
data_size += (uint32_t)_stream->ReadInt32() + sizeof(uint32_t);
else
data_size += hdr.Width * hdr.Height * hdr.BPP;
// Seek back and read all at once
data.resize(data_size);
_stream->Seek(data_pos, kSeekBegin);
_stream->Read(&data[0], data_size);
_curPos = index + 1; // mark correct pos
return HError::None();
}
void SpriteFile::SeekToSprite(sprkey_t index) {
// If we didn't just load the previous sprite, seek to it
if (index != _curPos) {
_stream->Seek(_spriteData[index].Offset, kSeekBegin);
_curPos = index;
}
}
// Finds the topmost occupied slot index
static sprkey_t FindTopmostSprite(const std::vector<std::pair<bool, Bitmap *>> &sprites) {
sprkey_t topmost = -1;
for (sprkey_t i = 0; i < static_cast<sprkey_t>(sprites.size()); ++i)
if (sprites[i].first)
topmost = i;
return topmost;
}
int SaveSpriteFile(const String &save_to_file,
const std::vector<std::pair<bool, Bitmap *> > &sprites,
SpriteFile *read_from_file,
int store_flags, SpriteCompression compress, SpriteFileIndex &index) {
std::unique_ptr<Stream> output(File::CreateFile(save_to_file));
if (output == nullptr)
return -1;
sprkey_t lastslot = FindTopmostSprite(sprites);
SpriteFileWriter writer(output);
writer.Begin(store_flags, compress, lastslot);
std::unique_ptr<Bitmap> temp_bmp; // for disposing temp sprites
std::vector<uint8_t> membuf; // for loading raw sprite data
const bool diff_compress =
read_from_file &&
(read_from_file->GetSpriteCompression() != compress ||
read_from_file->GetStoreFlags() != store_flags);
for (sprkey_t i = 0; i <= lastslot; ++i) {
if (!sprites[i].first) { // empty slot
writer.WriteEmptySlot();
continue;
}
Bitmap *image = sprites[i].second;
// if compression setting is different, load the sprite into memory
// (otherwise we will be able to simply copy bytes from one file to another
if ((image == nullptr) && diff_compress) {
read_from_file->LoadSprite(i, image);
temp_bmp.reset(image);
}
// if managed to load an image - save it according the new compression settings
if (image != nullptr) {
writer.WriteBitmap(image);
continue;
} else if (diff_compress) {
// sprite doesn't exist
writer.WriteEmptySlot();
continue;
}
// Not in memory - and same compression option;
// Directly copy the sprite bytes from the input file to the output
SpriteDatHeader hdr;
read_from_file->LoadRawData(i, hdr, membuf);
if (hdr.BPP == 0) { // empty slot
writer.WriteEmptySlot();
continue;
}
writer.WriteRawData(hdr, &membuf[0], membuf.size());
}
writer.Finalize();
index = writer.GetIndex();
return 0;
}
int SaveSpriteIndex(const String &filename, const SpriteFileIndex &index) {
// write the sprite index file
Stream *out = File::CreateFile(filename);
if (!out)
return -1;
// write "SPRINDEX" id
out->WriteArray(spindexid, strlen(spindexid), 1);
// write version
out->WriteInt32(kSpridxfVersion_Current);
out->WriteInt32(index.SpriteFileIDCheck);
// write last sprite number and num sprites, to verify that
// it matches the spr file
out->WriteInt32(index.GetLastSlot());
out->WriteInt32(index.GetCount());
if (index.GetCount() > 0) {
out->WriteArrayOfInt16(&index.Widths[0], index.Widths.size());
out->WriteArrayOfInt16(&index.Heights[0], index.Heights.size());
out->WriteArrayOfInt64(&index.Offsets[0], index.Offsets.size());
}
delete out;
return 0;
}
SpriteFileWriter::SpriteFileWriter(std::unique_ptr<Stream> &out) : _out(out) {
}
void SpriteFileWriter::Begin(int store_flags, SpriteCompression compress, sprkey_t last_slot) {
if (!_out) return;
_index.SpriteFileIDCheck = g_system->getMillis();
_storeFlags = store_flags;
_compress = compress;
// sprite file version
_out->WriteInt16(kSprfVersion_Current);
_out->WriteArray(spriteFileSig, strlen(spriteFileSig), 1);
_out->WriteInt8(_compress ? 1 : 0);
_out->WriteInt32(_index.SpriteFileIDCheck);
// Remember and write provided "last slot" index,
// but if it's not set (< 0) then we will have to return back later
// and write correct one; this is done in Finalize().
_lastSlotPos = _out->GetPosition();
_out->WriteInt32(last_slot);
_out->WriteInt8(_storeFlags);
_out->WriteInt8(0); // reserved
_out->WriteInt8(0);
_out->WriteInt8(0);
if (last_slot >= 0) { // allocate buffers to store the indexing info
sprkey_t numsprits = last_slot + 1;
_index.Offsets.reserve(numsprits);
_index.Widths.reserve(numsprits);
_index.Heights.reserve(numsprits);
}
}
void SpriteFileWriter::WriteBitmap(Bitmap *image) {
if (!_out) return;
int bpp = image->GetBPP();
int w = image->GetWidth();
int h = image->GetHeight();
ImBufferCPtr im_data(image->GetData(), w * h * bpp, bpp);
// (Optional) Handle storage options
std::vector<uint8_t> indexed_buf;
uint32_t palette[256];
uint32_t pal_count = 0;
SpriteFormat sformat = kSprFmt_Undefined;
if ((_storeFlags & kSprStore_OptimizeForSize) != 0 && (image->GetBPP() > 1)) { // Try to store this sprite as an indexed bitmap
uint32_t gen_pal_count;
if (CreateIndexedBitmap(image, indexed_buf, palette, gen_pal_count) && gen_pal_count > 0) { // Test the resulting size, and switch if the paletted image is less
if (im_data.Size > (indexed_buf.size() + gen_pal_count * image->GetBPP())) {
im_data = ImBufferCPtr(&indexed_buf[0], indexed_buf.size(), 1);
sformat = PaletteFormatForBPP(image->GetBPP());
pal_count = gen_pal_count;
}
}
}
// (Optional) Compress the image data into the temp buffer
SpriteCompression compress = kSprCompress_None;
if (_compress != kSprCompress_Deflate)
warning("TODO: Deflate not implemented, writing uncompressed BMP");
else if (_compress != kSprCompress_None) {
// TODO: rewrite this to only make a choice once the SpriteFile is initialized
// and use either function ptr or a decompressing stream class object
compress = _compress;
VectorStream mems(_membuf, kStream_Write);
bool result;
switch (compress) {
case kSprCompress_RLE: result = rle_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems);
break;
case kSprCompress_LZW: result = lzw_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems);
break;
case kSprCompress_Deflate: result = deflate_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems);
break;
default: assert(!"Unsupported compression type!"); result = false; break;
}
// mark to write as a plain byte array
im_data = result ? ImBufferCPtr(&_membuf[0], _membuf.size(), 1) : ImBufferCPtr();
}
// Write the final data
SpriteDatHeader hdr(bpp, sformat, pal_count, compress, w, h);
WriteSpriteData(hdr, im_data.Buf, im_data.Size, im_data.BPP, palette);
_membuf.clear();
}
static inline void WriteSprHeader(const SpriteDatHeader &hdr, Stream *out) {
out->WriteInt8(hdr.BPP);
out->WriteInt8(hdr.SFormat);
out->WriteInt8(hdr.PalCount > 0 ? (uint8_t)(hdr.PalCount - 1) : 0);
out->WriteInt8(hdr.Compress);
out->WriteInt16(hdr.Width);
out->WriteInt16(hdr.Height);
}
void SpriteFileWriter::WriteSpriteData(const SpriteDatHeader &hdr,
const uint8_t *im_data, size_t im_data_sz, int im_bpp,
const uint32_t palette[256]) {
// Add index entry and write resulting data to the stream
soff_t sproff = _out->GetPosition();
_index.Offsets.push_back(sproff);
_index.Widths.push_back(hdr.Width);
_index.Heights.push_back(hdr.Height);
WriteSprHeader(hdr, _out.get());
// write palette, if available
int pal_bpp = GetPaletteBPP(hdr.SFormat);
if (pal_bpp > 0) {
assert(hdr.PalCount > 0);
switch (pal_bpp) {
case 2: for (uint32_t i = 0; i < hdr.PalCount; ++i) {
_out->WriteInt16(palette[i]);
}
break;
case 4: for (uint32_t i = 0; i < hdr.PalCount; ++i) {
_out->WriteInt32(palette[i]);
}
break;
}
}
// write the image pixel data
_out->WriteInt32(im_data_sz);
switch (im_bpp) {
case 1: _out->Write(im_data, im_data_sz);
break;
case 2: _out->WriteArrayOfInt16(reinterpret_cast<const int16_t *>(im_data),
im_data_sz / sizeof(int16_t));
break;
case 4: _out->WriteArrayOfInt32(reinterpret_cast<const int32_t *>(im_data),
im_data_sz / sizeof(int32_t));
break;
default: assert(0); break;
}
}
void SpriteFileWriter::WriteEmptySlot() {
if (!_out) return;
soff_t sproff = _out->GetPosition();
_out->WriteInt16(0); // write invalid color depth to mark empty slot
_index.Offsets.push_back(sproff);
_index.Widths.push_back(0);
_index.Heights.push_back(0);
}
void SpriteFileWriter::WriteRawData(const SpriteDatHeader &hdr, const uint8_t *data, size_t data_sz) {
if (!_out) return;
soff_t sproff = _out->GetPosition();
_index.Offsets.push_back(sproff);
_index.Widths.push_back(hdr.Width);
_index.Heights.push_back(hdr.Height);
WriteSprHeader(hdr, _out.get());
_out->Write(data, data_sz);
}
void SpriteFileWriter::Finalize() {
if (!_out || _lastSlotPos < 0) return;
_out->Seek(_lastSlotPos, kSeekBegin);
_out->WriteInt32(_index.GetLastSlot());
_out.reset();
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,237 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// SpriteFile class handles sprite file parsing and streaming sprites.
// SpriteFileWriter manages writing sprites into the output stream one by one,
// accumulating index information, and may therefore be suitable for a variety
// of situations.
//
//=============================================================================
#ifndef AGS_SHARED_AC_SPRITE_FILE_H
#define AGS_SHARED_AC_SPRITE_FILE_H
#include "ags/shared/core/types.h"
#include "common/std/memory.h"
#include "common/std/vector.h"
#include "ags/shared/util/stream.h"
#include "ags/globals.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class Bitmap;
// TODO: research old version differences
enum SpriteFileVersion {
kSprfVersion_Undefined = 0,
kSprfVersion_Uncompressed = 4,
kSprfVersion_Compressed = 5,
kSprfVersion_Last32bit = 6,
kSprfVersion_64bit = 10,
kSprfVersion_HighSpriteLimit = 11,
kSprfVersion_StorageFormats = 12,
kSprfVersion_Current = kSprfVersion_StorageFormats
};
enum SpriteIndexFileVersion {
kSpridxfVersion_Initial = 1,
kSpridxfVersion_Last32bit = 2,
kSpridxfVersion_64bit = 10,
kSpridxfVersion_HighSpriteLimit = 11,
kSpridxfVersion_Current = kSpridxfVersion_HighSpriteLimit
};
// Instructions to how the sprites are allowed to be stored
enum SpriteStorage {
// When possible convert the sprite into another format for less disk space
// e.g. save 16/32-bit images as 8-bit colormaps with palette
kSprStore_OptimizeForSize = 0x01
};
// Format in which the sprite's pixel data is stored
enum SpriteFormat {
kSprFmt_Undefined = 0, // undefined, or keep as-is
// Encoded as a 8-bit colormap with palette of 24-bit RGB values
kSprFmt_PaletteRgb888 = 32,
// Encoded as a 8-bit colormap with palette of 32-bit ARGB values
kSprFmt_PaletteArgb8888 = 33,
// Encoded as a 8-bit colormap with palette of 16-bit RGB565 values
kSprFmt_PaletteRgb565 = 34
};
enum SpriteCompression {
kSprCompress_None = 0,
kSprCompress_RLE,
kSprCompress_LZW,
kSprCompress_Deflate
};
typedef int32_t sprkey_t;
// SpriteFileIndex contains sprite file's table of contents
struct SpriteFileIndex {
int SpriteFileIDCheck = 0; // tag matching sprite file and index file
std::vector<int16_t> Widths;
std::vector<int16_t> Heights;
std::vector<soff_t> Offsets;
inline size_t GetCount() const {
return Offsets.size();
}
inline sprkey_t GetLastSlot() const {
return (sprkey_t)GetCount() - 1;
}
};
// Invidual sprite data header (as read from the file)
struct SpriteDatHeader {
int BPP = 0; // color depth (bytes per pixel); or input format
SpriteFormat SFormat = kSprFmt_Undefined; // storage format
uint32_t PalCount = 0; // palette length, if applicable to storage format
SpriteCompression Compress = kSprCompress_None; // compression type
int Width = 0; // sprite's width
int Height = 0; // sprite's height
SpriteDatHeader() = default;
SpriteDatHeader(int bpp, SpriteFormat sformat = kSprFmt_Undefined,
uint32_t pal_count = 0, SpriteCompression compress = kSprCompress_None,
int w = 0, int h = 0) : BPP(bpp), SFormat(sformat), PalCount(pal_count),
Compress(compress), Width(w), Height(h) {
}
};
// SpriteFile opens a sprite file for reading, reports general information,
// and lets read sprites in any order.
class SpriteFile {
public:
// Standart sprite file and sprite index names
static const char *DefaultSpriteFileName;
static const char *DefaultSpriteIndexName;
SpriteFile();
// Loads sprite reference information and inits sprite stream
HError OpenFile(const String &filename, const String &sprindex_filename,
std::vector<Size> &metrics);
// Closes stream; no reading will be possible unless opened again
void Close();
int GetStoreFlags() const;
// Tells if bitmaps in the file are compressed
SpriteCompression GetSpriteCompression() const;
// Tells the highest known sprite index
sprkey_t GetTopmostSprite() const;
// Loads sprite index file
bool LoadSpriteIndexFile(const String &filename, int expectedFileID,
soff_t spr_initial_offs, sprkey_t topmost, std::vector<Size> &metrics);
// Rebuilds sprite index from the main sprite file
HError RebuildSpriteIndex(Stream *in, sprkey_t topmost,
std::vector<Size> &metrics);
// Loads an image data and creates a ready bitmap
HError LoadSprite(sprkey_t index, Bitmap *&sprite);
// Loads a raw sprite element data into the buffer, stores header info separately
HError LoadRawData(sprkey_t index, SpriteDatHeader &hdr, std::vector<uint8_t> &data);
private:
// Seek stream to sprite
void SeekToSprite(sprkey_t index);
// Internal sprite reference
struct SpriteRef {
soff_t Offset = 0; // data offset
size_t RawSize = 0; // file size of element, in bytes
// TODO: RawSize is currently unused, due to incompleteness of spriteindex format
};
// Array of sprite references
std::vector<SpriteRef> _spriteData;
std::unique_ptr<Stream> _stream; // the sprite stream
SpriteFileVersion _version = kSprfVersion_Current;
int _storeFlags = 0; // storage flags, specify how sprites may be stored
SpriteCompression _compress = kSprCompress_None; // sprite compression typ
sprkey_t _curPos; // current stream position (sprite slot)
};
// SpriteFileWriter class writes a sprite file in a requested format.
// Start using it by calling Begin, write ready bitmaps or copy raw sprite data
// over slot by slot, then call Finalize to let it close the format correctly.
class SpriteFileWriter {
public:
SpriteFileWriter(std::unique_ptr<Stream> &out);
~SpriteFileWriter() {
}
// Get the sprite index, accumulated after write
const SpriteFileIndex &GetIndex() const {
return _index;
}
// Initializes new sprite file format;
// store_flags are SpriteStorage;
// optionally hint how many sprites will be written.
void Begin(int store_flags, SpriteCompression compress, sprkey_t last_slot = -1);
// Writes a bitmap into file, compressing if necessary
void WriteBitmap(Bitmap *image);
// Writes an empty slot marker
void WriteEmptySlot();
// Writes a raw sprite data without any additional processing
void WriteRawData(const SpriteDatHeader &hdr, const uint8_t *data, size_t data_sz);
// Finalizes current format; no further writing is possible after this
void Finalize();
private:
// Writes prepared image data in a proper file format, following explicit data_bpp rule
void WriteSpriteData(const SpriteDatHeader &hdr,
const uint8_t *im_data, size_t im_data_sz, int im_bpp,
const uint32_t palette[256]);
std::unique_ptr<Stream> &_out;
int _storeFlags = 0;
SpriteCompression _compress = kSprCompress_None;
soff_t _lastSlotPos = -1; // last slot save position in file
// sprite index accumulated on write for reporting back to user
SpriteFileIndex _index;
// compression buffer
std::vector<uint8_t> _membuf;
};
// Saves all sprites to file; fills in index data for external use.
// TODO: refactor to be able to save main file and index file separately (separate function for gather data?)
// Accepts available sprites as pairs of bool and Bitmap pointer, where boolean value
// tells if sprite exists and Bitmap pointer may be null;
// If a sprite's bitmap is missing, it will try reading one from the input file stream.
int SaveSpriteFile(const String &save_to_file,
const std::vector<std::pair<bool, Bitmap *> > &sprites,
SpriteFile *read_from_file, // optional file to read missing sprites from
int store_flags, SpriteCompression compress, SpriteFileIndex &index);
// Saves sprite index table in a separate file
extern int SaveSpriteIndex(const String &filename, const SpriteFileIndex &index);
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,179 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/ac/view.h"
#include "ags/shared/util/stream.h"
namespace AGS3 {
using AGS::Shared::Stream;
ViewFrame::ViewFrame()
: pic(0)
, xoffs(0)
, yoffs(0)
, speed(0)
, flags(0)
, sound(-1)
, audioclip(-1) {
reserved_for_future[0] = 0;
reserved_for_future[1] = 0;
}
void ViewFrame::ReadFromFile(Stream *in) {
pic = in->ReadInt32();
xoffs = in->ReadInt16();
yoffs = in->ReadInt16();
speed = in->ReadInt16();
in->ReadInt16(); // alignment padding to int32
flags = in->ReadInt32();
sound = in->ReadInt32();
in->ReadInt32(); // reserved 1
in->ReadInt32(); // reserved 1
}
void ViewFrame::WriteToFile(Stream *out) {
out->WriteInt32(pic);
out->WriteInt16(xoffs);
out->WriteInt16(yoffs);
out->WriteInt16(speed);
out->WriteInt16(0); // alignment padding to int32
out->WriteInt32(flags);
out->WriteInt32(sound);
out->WriteInt32(0);
out->WriteInt32(0);
}
ViewLoopNew::ViewLoopNew()
: numFrames(0)
, flags(0) {
}
bool ViewLoopNew::RunNextLoop() {
return (flags & LOOPFLAG_RUNNEXTLOOP);
}
void ViewLoopNew::Initialize(int frameCount) {
numFrames = frameCount;
flags = 0;
// an extra frame is allocated to prevent crashes with empty loops
frames.resize(numFrames > 0 ? numFrames : 1);
}
void ViewLoopNew::Dispose() {
frames.clear();
numFrames = 0;
}
void ViewLoopNew::WriteToFile_v321(Stream *out) {
out->WriteInt16(static_cast<uint16_t>(numFrames));
out->WriteInt32(flags);
WriteFrames(out);
}
void ViewLoopNew::WriteFrames(Stream *out) {
for (int i = 0; i < numFrames; ++i) {
frames[i].WriteToFile(out);
}
}
void ViewLoopNew::ReadFromFile_v321(Stream *in) {
Initialize(static_cast<uint16_t>(in->ReadInt16()));
flags = in->ReadInt32();
ReadFrames(in);
}
void ViewLoopNew::ReadFrames(Stream *in) {
for (int i = 0; i < numFrames; ++i) {
frames[i].ReadFromFile(in);
}
}
ViewStruct::ViewStruct()
: numLoops(0) {
}
void ViewStruct::Initialize(int loopCount) {
numLoops = loopCount;
loops.resize(numLoops);
}
void ViewStruct::Dispose() {
loops.clear();
numLoops = 0;
}
void ViewStruct::WriteToFile(Stream *out) {
out->WriteInt16(static_cast<uint16_t>(numLoops));
for (int i = 0; i < numLoops; i++) {
loops[i].WriteToFile_v321(out);
}
}
void ViewStruct::ReadFromFile(Stream *in) {
Initialize(static_cast<uint16_t>(in->ReadInt16()));
for (int i = 0; i < numLoops; i++) {
loops[i].ReadFromFile_v321(in);
}
}
ViewStruct272::ViewStruct272()
: numloops(0) {
memset(numframes, 0, sizeof(numframes));
memset(loopflags, 0, sizeof(loopflags));
}
void ViewStruct272::ReadFromFile(Stream *in) {
numloops = in->ReadInt16();
for (int i = 0; i < 16; ++i) {
numframes[i] = in->ReadInt16();
}
in->ReadInt16(); // alignment padding to int32
in->ReadArrayOfInt32(loopflags, 16);
for (int j = 0; j < 16; ++j) {
for (int i = 0; i < 20; ++i) {
frames[j][i].ReadFromFile(in);
}
}
}
void Convert272ViewsToNew(const std::vector<ViewStruct272> &oldv, std::vector<ViewStruct> &newv) {
for (size_t a = 0; a < oldv.size(); a++) {
newv[a].Initialize(oldv[a].numloops);
for (int b = 0; b < oldv[a].numloops; b++) {
newv[a].loops[b].Initialize(oldv[a].numframes[b]);
if ((oldv[a].numframes[b] > 0) &&
(oldv[a].frames[b][oldv[a].numframes[b] - 1].pic == -1)) {
newv[a].loops[b].flags = LOOPFLAG_RUNNEXTLOOP;
newv[a].loops[b].numFrames--;
} else
newv[a].loops[b].flags = 0;
for (int c = 0; c < newv[a].loops[b].numFrames; c++)
newv[a].loops[b].frames[c] = oldv[a].frames[b][c];
}
}
}
} // namespace AGS3

View File

@@ -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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_AC_VIEW_H
#define AGS_SHARED_AC_VIEW_H
#include "common/std/vector.h"
#include "ags/shared/core/types.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class Stream;
} // namespace Shared
} // namespace AGS
using namespace AGS; // FIXME later
#define VFLG_FLIPSPRITE 1
struct ViewFrame {
int pic;
short xoffs, yoffs;
short speed;
int flags; // VFLG_* flags
int sound; // play sound when this frame comes round
int reserved_for_future[2]; // kept only for plugin api
// not saved, set at runtime only
int audioclip; // actual audio clip reference (in case sound is a legacy number)
ViewFrame();
void ReadFromFile(Shared::Stream *in);
void WriteToFile(Shared::Stream *out);
};
#define LOOPFLAG_RUNNEXTLOOP 1
struct ViewLoopNew {
int numFrames;
int flags;
std::vector<ViewFrame> frames;
// NOTE: we still need numFrames:
// as we always allocate at least 1 frame for safety, to avoid crashes,
// but have to report "logical" number of frames for the engine API.
ViewLoopNew();
void Initialize(int frameCount);
void Dispose();
bool RunNextLoop();
void WriteToFile_v321(Shared::Stream *out);
void ReadFromFile_v321(Shared::Stream *in);
void WriteFrames(Shared::Stream *out);
void ReadFrames(Shared::Stream *in);
};
struct ViewStruct {
int numLoops;
std::vector<ViewLoopNew> loops;
ViewStruct();
void Initialize(int loopCount);
void Dispose();
void WriteToFile(Shared::Stream *out);
void ReadFromFile(Shared::Stream *in);
};
struct ViewStruct272 {
short numloops;
short numframes[16];
int32_t loopflags[16];
ViewFrame frames[16][20];
ViewStruct272();
void ReadFromFile(Shared::Stream *in);
};
extern void Convert272ViewsToNew(const std::vector<ViewStruct272> &oldv, std::vector<ViewStruct> &newv);
} // namespace AGS3
#endif

View File

@@ -0,0 +1,185 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/std/algorithm.h"
#include "ags/shared/ac/words_dictionary.h"
#include "ags/shared/util/stream.h"
#include "ags/shared/util/string_compat.h"
#include "ags/globals.h"
namespace AGS3 {
using namespace AGS::Shared;
WordsDictionary::WordsDictionary()
: num_words(0)
, word(nullptr)
, wordnum(nullptr) {
}
WordsDictionary::~WordsDictionary() {
free_memory();
}
void WordsDictionary::allocate_memory(int wordCount) {
num_words = wordCount;
if (num_words > 0) {
word = new char *[wordCount];
word[0] = new char[wordCount * MAX_PARSER_WORD_LENGTH];
wordnum = new short[wordCount];
for (int i = 1; i < wordCount; i++) {
word[i] = word[0] + MAX_PARSER_WORD_LENGTH * i;
}
}
}
void WordsDictionary::free_memory() {
if (num_words > 0) {
delete[] word[0];
delete[] word;
delete[] wordnum;
word = nullptr;
wordnum = nullptr;
num_words = 0;
}
}
void WordsDictionary::sort() {
int aa, bb;
for (aa = 0; aa < num_words; aa++) {
for (bb = aa + 1; bb < num_words; bb++) {
if (((wordnum[aa] == wordnum[bb]) && (ags_stricmp(word[aa], word[bb]) > 0))
|| (wordnum[aa] > wordnum[bb])) {
short temp = wordnum[aa];
char tempst[30];
wordnum[aa] = wordnum[bb];
wordnum[bb] = temp;
snprintf(tempst, MAX_PARSER_WORD_LENGTH, "%s", word[aa]);
snprintf(word[aa], MAX_PARSER_WORD_LENGTH, "%s", word[bb]);
snprintf(word[bb], MAX_PARSER_WORD_LENGTH, "%s", tempst);
bb = aa;
}
}
}
}
int WordsDictionary::find_index(const char *wrem) {
int aa;
for (aa = 0; aa < num_words; aa++) {
if (ags_stricmp(wrem, word[aa]) == 0)
return aa;
}
return -1;
}
void decrypt_text(char *toenc, size_t buf_sz) {
int adx = 0;
const char *p_end = toenc + buf_sz;
while (toenc < p_end) {
toenc[0] -= _G(passwencstring)[adx];
if (toenc[0] == 0)
break;
adx++;
toenc++;
if (adx > 10)
adx = 0;
}
}
void read_string_decrypt(Stream *in, char *buf, size_t buf_sz) {
size_t len = in->ReadInt32();
size_t slen = MIN(buf_sz - 1, len);
in->Read(buf, slen);
if (len > slen)
in->Seek(len - slen);
decrypt_text(buf, slen);
buf[slen] = 0;
}
String read_string_decrypt(Stream *in, std::vector<char> &dec_buf) {
size_t len = in->ReadInt32();
dec_buf.resize(len + 1);
in->Read(dec_buf.data(), len);
decrypt_text(dec_buf.data(), len);
dec_buf.back() = 0; // null terminate in case read string does not have one
return String(dec_buf.data());
}
void read_dictionary(WordsDictionary *dict, Stream *out) {
int ii;
dict->allocate_memory(out->ReadInt32());
for (ii = 0; ii < dict->num_words; ii++) {
read_string_decrypt(out, dict->word[ii], MAX_PARSER_WORD_LENGTH);
dict->wordnum[ii] = out->ReadInt16();
}
}
#if defined (OBSOLETE)
// TODO: not a part of wordsdictionary, move to obsoletes
void freadmissout(short *pptr, Stream *in) {
in->ReadArrayOfInt16(&pptr[0], 5);
in->ReadArrayOfInt16(&pptr[7], NUM_CONDIT - 7);
pptr[5] = pptr[6] = 0;
}
#endif
void encrypt_text(char *toenc) {
int adx = 0, tobreak = 0;
while (tobreak == 0) {
if (toenc[0] == 0)
tobreak = 1;
toenc[0] += _G(passwencstring)[adx];
adx++;
toenc++;
if (adx > 10)
adx = 0;
}
}
void write_string_encrypt(Stream *out, const char *s) {
int stlent = (int)strlen(s) + 1;
out->WriteInt32(stlent);
char *enc = ags_strdup(s);
encrypt_text(enc);
out->WriteArray(enc, stlent, 1);
free(enc);
}
void write_dictionary(WordsDictionary *dict, Stream *out) {
int ii;
out->WriteInt32(dict->num_words);
for (ii = 0; ii < dict->num_words; ii++) {
write_string_encrypt(out, dict->word[ii]);
out->WriteInt16(dict->wordnum[ii]);
}
}
} // namespace AGS3

View File

@@ -0,0 +1,76 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_AC_WORDS_DICTIONARY_H
#define AGS_SHARED_AC_WORDS_DICTIONARY_H
#include "common/std/vector.h"
#include "ags/shared/core/types.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class Stream;
} // namespace Shared
} // namespace AGS
using namespace AGS; // FIXME later
#define MAX_PARSER_WORD_LENGTH 30
#define ANYWORD 29999
#define RESTOFLINE 30000
struct WordsDictionary {
int num_words;
char **word;
short *wordnum;
WordsDictionary();
~WordsDictionary();
void allocate_memory(int wordCount);
void free_memory();
void sort();
int find_index(const char *);
};
// Decrypts text found in the given buffer, writes back to the same buffer
extern void decrypt_text(char *buf, size_t buf_sz);
// Reads an encrypted string from the stream and decrypts into the provided buffer
extern void read_string_decrypt(Shared::Stream *in, char *buf, size_t buf_sz);
// Reads an encrypted string from the stream and returns as a string;
// uses provided vector as a temporary decryption buffer (avoid extra allocs)
extern Shared::String read_string_decrypt(Shared::Stream *in, std::vector<char> &dec_buf);
extern void read_dictionary(WordsDictionary *dict, Shared::Stream *in);
#if defined (OBSOLETE)
// TODO: not a part of wordsdictionary, move to obsoletes
extern void freadmissout(short *pptr, Shared::Stream *in);
#endif
extern void encrypt_text(char *toenc);
extern void write_string_encrypt(Shared::Stream *out, const char *s);
extern void write_dictionary(WordsDictionary *dict, Shared::Stream *out);
} // namespace AGS3
#endif

View File

@@ -0,0 +1,36 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/core/asset.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
AssetInfo::AssetInfo()
: LibUid(0)
, Offset(0)
, Size(0) {
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,64 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// AssetInfo and AssetLibInfo - classes describing generic asset library.
//
//=============================================================================
#ifndef AGS_SHARED_CORE_ASSET_H
#define AGS_SHARED_CORE_ASSET_H
#include "common/std/vector.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
// Information on single asset
struct AssetInfo {
// A pair of filename and libuid is assumed to be unique in game scope
String FileName; // filename associated with asset
int32_t LibUid; // index of library partition (separate file)
soff_t Offset; // asset's position in library file (in bytes)
soff_t Size; // asset's size (in bytes)
AssetInfo();
};
// Information on multifile asset library
struct AssetLibInfo {
String BasePath; // full path to the base filename
String BaseDir; // library's directory
String BaseFileName; // library's base (head) filename
std::vector<String> LibFileNames; // filename for each library part
// Library contents
std::vector<AssetInfo> AssetInfos; // information on contained assets
};
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,304 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/memstream.h"
#include "common/std/algorithm.h"
#include "common/std/utility.h"
#include "ags/shared/core/platform.h"
#include "ags/shared/core/asset_manager.h"
#include "ags/shared/util/directory.h"
#include "ags/shared/util/multi_file_lib.h"
#include "ags/shared/util/path.h"
#include "ags/shared/util/string_utils.h" // cbuf_to_string_and_free
namespace AGS3 {
namespace AGS {
namespace Shared {
inline static bool IsAssetLibDir(const AssetLibInfo *lib) {
return lib->BaseFileName.IsEmpty();
}
bool AssetManager::AssetLibEx::TestFilter(const String &filter) const {
return filter == "*" ||
(std::find(Filters.begin(), Filters.end(), filter) != Filters.end());
}
// Asset library sorting function, directories have priority
bool SortLibsPriorityDir(const AssetLibInfo *lib1, const AssetLibInfo *lib2) {
// first element is less if it's a directory while second is a lib
return IsAssetLibDir(lib1) && !IsAssetLibDir(lib2);
}
// Asset library sorting function, packages have priority
bool SortLibsPriorityLib(const AssetLibInfo *lib1, const AssetLibInfo *lib2) {
// first element is less if it's a lib while second is a directory
return !IsAssetLibDir(lib1) && IsAssetLibDir(lib2);
}
AssetManager::AssetManager() {
SetSearchPriority(kAssetPriorityDir); // ensure lib sorter is initialized
}
/* static */ bool AssetManager::IsDataFile(const String &data_file) {
Stream *in = File::OpenFileCI(data_file.GetCStr(), Shared::kFile_Open, Shared::kFile_Read);
if (in) {
MFLUtil::MFLError err = MFLUtil::TestIsMFL(in, true);
delete in;
return err == MFLUtil::kMFLNoError;
}
return false;
}
/* static */ AssetError AssetManager::ReadDataFileTOC(const String &data_file, AssetLibInfo &lib) {
Stream *in = File::OpenFileCI(data_file.GetCStr(), Shared::kFile_Open, Shared::kFile_Read);
if (in) {
MFLUtil::MFLError err = MFLUtil::ReadHeader(lib, in);
delete in;
return (err != MFLUtil::kMFLNoError) ? kAssetErrLibParse : kAssetNoError;
}
return kAssetErrNoLibFile;
}
void AssetManager::SetSearchPriority(AssetSearchPriority priority) {
_libsPriority = priority;
_libsSorter = _libsPriority == kAssetPriorityDir ? SortLibsPriorityDir : SortLibsPriorityLib;
std::sort(_activeLibs.begin(), _activeLibs.end(), _libsSorter);
}
AssetSearchPriority AssetManager::GetSearchPriority() const {
return _libsPriority;
}
AssetError AssetManager::AddLibrary(const String &path, const AssetLibInfo **out_lib) {
return AddLibrary(path, "", out_lib);
}
AssetError AssetManager::AddLibrary(const String &path, const String &filters, const AssetLibInfo **out_lib) {
if (path.IsEmpty())
return kAssetErrNoLibFile;
for (const auto &lib : _libs) {
if (Path::ComparePaths(lib->BasePath, path) == 0) {
// already present, only assign new filters
lib->Filters = filters.Split(',');
if (out_lib)
*out_lib = lib;
return kAssetNoError;
}
}
AssetLibEx *lib;
AssetError err = RegisterAssetLib(path, lib);
if (err != kAssetNoError)
return err;
lib->Filters = filters.Split(',');
auto place = std::upper_bound(_activeLibs.begin(), _activeLibs.end(), lib, _libsSorter);
_activeLibs.insert(place, lib);
if (out_lib)
*out_lib = lib;
return kAssetNoError;
}
void AssetManager::RemoveLibrary(const String &path) {
int idx = 0;
for (auto it = _libs.begin(); it != _libs.end(); ++it, ++idx) {
if (Path::ComparePaths((*it)->BasePath, path) == 0) {
_libs.remove_at(idx);
_activeLibs.remove(*it);
return;
}
}
}
void AssetManager::RemoveAllLibraries() {
for (uint i = 0; i < _libs.size(); ++i)
delete _libs[i];
_libs.clear();
_activeLibs.clear();
}
size_t AssetManager::GetLibraryCount() const {
return _libs.size();
}
const AssetLibInfo *AssetManager::GetLibraryInfo(size_t index) const {
return index < _libs.size() ? _libs[index] : nullptr;
}
bool AssetManager::DoesAssetExist(const String &asset_name, const String &filter) const {
for (const auto &lib : _activeLibs) {
if (!lib->TestFilter(filter))
continue; // filter does not match
if (IsAssetLibDir(lib)) {
String filename = File::FindFileCI(lib->BaseDir, asset_name);
if (!filename.IsEmpty() && File::IsFile(filename)) return true;
} else {
for (const auto &a : lib->AssetInfos) {
if (a.FileName.CompareNoCase(asset_name) == 0) return true;
}
}
}
return false;
}
void AssetManager::FindAssets(std::vector<String> &assets, const String &wildcard,
const String &filter) const {
String pattern = StrUtil::WildcardToRegex(wildcard);
for (const auto *lib : _activeLibs) {
auto match = std::find(lib->Filters.begin(), lib->Filters.end(), filter);
if (match == lib->Filters.end())
continue; // filter does not match
if (IsAssetLibDir(lib)) {
for (FindFile ff = FindFile::OpenFiles(lib->BaseDir, wildcard);
!ff.AtEnd(); ff.Next())
assets.push_back(ff.Current());
} else {
for (const auto &a : lib->AssetInfos) {
if (pattern == "*" || (*pattern.GetCStr() &&
Common::String(a.FileName.GetCStr()).hasSuffixIgnoreCase(pattern.GetCStr() + 1)))
assets.push_back(a.FileName);
}
}
}
// Sort and remove duplicates
std::sort(assets.begin(), assets.end());
assets.erase(std::unique(assets.begin(), assets.end()), assets.end());
}
AssetError AssetManager::RegisterAssetLib(const String &path, AssetLibEx *&out_lib) {
// Test for a directory
std::unique_ptr<AssetLibEx> lib;
if (File::IsDirectory(path)) {
lib.reset(new AssetLibEx());
lib->BasePath = Path::MakeAbsolutePath(path);
lib->BaseDir = Path::GetDirectoryPath(lib->BasePath);
// TODO: maybe parse directory for the file reference? idk if needed
}
// ...else try open a data library
else {
Stream *in = File::OpenFileCI(path.GetCStr(), Shared::kFile_Open, Shared::kFile_Read);
if (!in)
return kAssetErrNoLibFile; // can't be opened, return error code
lib.reset(new AssetLibEx());
MFLUtil::MFLError mfl_err = MFLUtil::ReadHeader(*lib, in);
delete in;
if (mfl_err != MFLUtil::kMFLNoError)
return kAssetErrLibParse;
lib->BasePath = Path::MakeAbsolutePath(path);
lib->BaseDir = Path::GetDirectoryPath(lib->BasePath);
lib->BaseFileName = Path::GetFilename(lib->BasePath);
lib->LibFileNames[0] = lib->BaseFileName;
// Find out real library files in the current filesystem and save them
for (size_t i = 0; i < lib->LibFileNames.size(); ++i) {
lib->RealLibFiles.push_back(File::FindFileCI(lib->BaseDir, lib->LibFileNames[i]));
}
}
out_lib = lib.release();
_libs.push_back(out_lib);
return kAssetNoError;
}
Stream *AssetManager::OpenAsset(const String &asset_name, const String &filter) const {
for (const auto *lib : _activeLibs) {
if (!lib->TestFilter(filter)) continue; // filter does not match
Stream *s = nullptr;
if (IsAssetLibDir(lib))
s = OpenAssetFromDir(lib, asset_name);
else
s = OpenAssetFromLib(lib, asset_name);
if (s)
return s;
}
return nullptr;
}
Stream *AssetManager::OpenAssetFromLib(const AssetLibEx *lib, const String &asset_name) const {
for (const auto &a : lib->AssetInfos) {
if (a.FileName.CompareNoCase(asset_name) == 0) {
String libfile = lib->RealLibFiles[a.LibUid];
if (libfile.IsEmpty())
return nullptr;
return File::OpenFile(libfile, a.Offset, a.Offset + a.Size);
}
}
return nullptr;
}
Stream *AssetManager::OpenAssetFromDir(const AssetLibEx *lib, const String &file_name) const {
String found_file = File::FindFileCI(lib->BaseDir, file_name);
if (found_file.IsEmpty())
return nullptr;
return File::OpenFileRead(found_file);
}
Stream *AssetManager::OpenAsset(const String &asset_name) const {
return OpenAsset(asset_name, "");
}
Common::SeekableReadStream *AssetManager::OpenAssetStream(const String &asset_name) const {
return OpenAssetStream(asset_name, "");
}
Common::SeekableReadStream *AssetManager::OpenAssetStream(const String &asset_name, const String &filter) const {
Stream *stream = OpenAsset(asset_name, filter);
if (!stream)
return nullptr;
// Get the contents of the asset
size_t assetSize = stream->GetLength();
byte *data = (byte *)malloc(assetSize);
stream->Read(data, assetSize);
delete stream;
return new Common::MemoryReadStream(data, assetSize, DisposeAfterUse::YES);
}
String GetAssetErrorText(AssetError err) {
switch (err) {
case kAssetNoError:
return "No error.";
case kAssetErrNoLibFile:
return "Asset library file not found or could not be opened.";
case kAssetErrLibParse:
return "Not an asset library or unsupported format.";
case kAssetErrNoManager:
return "Asset manager is not initialized.";
}
return "Unknown error.";
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,160 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// Asset manager class for reading and writing game resources.
//-----------------------------------------------------------------------------
//
// The code is based on CLIB32, by Chris Jones (1998-99), DJGPP implementation
// of the CLIB reader.
//
//-----------------------------------------------------------------------------
// TODO: consider replace/merge with PhysFS library in the future.
//
// TODO: support streams that work on a file subsection, limited by size,
// to avoid having to return an asset size separately from a stream.
// TODO: return stream as smart pointer.
//
//=============================================================================
#ifndef AGS_SHARED_CORE_ASSET_MANAGER_H
#define AGS_SHARED_CORE_ASSET_MANAGER_H
#include "common/stream.h"
#include "common/std/functional.h"
#include "common/std/memory.h"
#include "ags/shared/core/asset.h"
#include "ags/shared/util/file.h" // TODO: extract filestream mode constants or introduce generic ones
namespace AGS3 {
namespace AGS {
namespace Shared {
class Stream;
struct MultiFileLib;
enum AssetSearchPriority {
kAssetPriorityDir,
kAssetPriorityLib
};
enum AssetError {
kAssetNoError = 0,
kAssetErrNoLibFile = -1, // library file not found or can't be read
kAssetErrLibParse = -2, // bad library file format or read error
kAssetErrNoManager = -6, // asset manager not initialized
};
/**
* AssetPath combines asset name and optional library filter, that serves to narrow down the search
*/
struct AssetPath {
String Name;
String Filter;
AssetPath(const String &name = "", const String &filter = "") : Name(name), Filter(filter) {
}
};
class AssetManager {
public:
AssetManager();
~AssetManager() {
RemoveAllLibraries();
}
// Test if given file is main data file
static bool IsDataFile(const String &data_file);
// Read data file table of contents into provided struct
static AssetError ReadDataFileTOC(const String &data_file, AssetLibInfo &lib);
// Sets asset search priority (in which order manager will search available locations)
void SetSearchPriority(AssetSearchPriority priority);
// Gets current asset search priority
AssetSearchPriority GetSearchPriority() const;
// Add library location to the list of asset locations
AssetError AddLibrary(const String &path, const AssetLibInfo **lib = nullptr);
// Add library location, specifying comma-separated list of filters;
// if library was already added before, this method will overwrite the filters only
AssetError AddLibrary(const String &path, const String &filters, const AssetLibInfo **lib = nullptr);
// Remove library location from the list of asset locations
void RemoveLibrary(const String &path);
// Removes all libraries
void RemoveAllLibraries();
size_t GetLibraryCount() const;
const AssetLibInfo *GetLibraryInfo(size_t index) const;
// Tells whether asset exists in any of the registered search locations
bool DoesAssetExist(const String &asset_name, const String &filter = "") const;
inline bool DoesAssetExist(const AssetPath &apath) const {
return DoesAssetExist(apath.Name, apath.Filter);
}
// Searches in all the registered locations and collects a list of
// assets using given wildcard pattern
void FindAssets(std::vector<String> &assets, const String &wildcard,
const String &filter = "") const;
// Open asset stream in the given work mode; returns null if asset is not found or cannot be opened
// This method only searches in libraries that do not have any defined filters
Stream *OpenAsset(const String &asset_name) const;
// Open asset stream, providing a single filter to search in matching libraries
Stream *OpenAsset(const String &asset_name, const String &filter) const;
inline Stream *OpenAsset(const AssetPath &apath) const {
return OpenAsset(apath.Name, apath.Filter);
}
// Open asset stream in the given work mode; returns null if asset is not found or cannot be opened
// This method only searches in libraries that do not have any defined filters
Common::SeekableReadStream *OpenAssetStream(const String &asset_name) const;
// Open asset stream, providing a single filter to search in matching libraries
Common::SeekableReadStream *OpenAssetStream(const String &asset_name, const String &filter) const;
private:
// AssetLibEx combines library info with extended internal data required for the manager
struct AssetLibEx : AssetLibInfo {
std::vector<String> Filters; // asset filters this library is matching to
std::vector<String> RealLibFiles; // fixed up library filenames
bool TestFilter(const String &filter) const;
};
// Loads library and registers its contents into the cache
AssetError RegisterAssetLib(const String &path, AssetLibEx *&lib);
// Tries to find asset in the given location, and then opens a stream for reading
Stream *OpenAssetFromLib(const AssetLibEx *lib, const String &asset_name) const;
Stream *OpenAssetFromDir(const AssetLibEx *lib, const String &asset_name) const;
std::vector<AssetLibEx *> _libs;
std::vector<AssetLibEx *> _activeLibs;
AssetSearchPriority _libsPriority = kAssetPriorityDir;
// Sorting function, depends on priority setting
bool (*_libsSorter)(const AssetLibInfo *, const AssetLibInfo *);
};
String GetAssetErrorText(AssetError err);
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,34 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_CORE_DEFVERSION_H
#define AGS_SHARED_CORE_DEFVERSION_H
#define ACI_VERSION_STR "3.6.1.33"
#if defined (RC_INVOKED) // for MSVC resource compiler
#define ACI_VERSION_MSRC_DEF 3.6.1.33
#endif
#define SPECIAL_VERSION ""
#define ACI_COPYRIGHT_YEARS "2011-2025"
#endif

View File

@@ -0,0 +1,164 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_CORE_PLATFORM_H
#define AGS_SHARED_CORE_PLATFORM_H
#include "common/scummsys.h"
namespace AGS3 {
// platform definitions. Not intended for replacing types or checking for libraries.
// ScummVM implementation is identifying as Linux for now
#if 1
#define AGS_PLATFORM_SCUMMVM (1)
#define AGS_PLATFORM_OS_WINDOWS (0)
#define AGS_PLATFORM_OS_LINUX (1)
#define AGS_PLATFORM_OS_MACOS (0)
#define AGS_PLATFORM_OS_ANDROID (0)
#define AGS_PLATFORM_OS_IOS (0)
#define AGS_PLATFORM_OS_PSP (0)
#define AGS_PLATFORM_OS_EMSCRIPTEN (0)
// check Android first because sometimes it can get confused with host OS
#elif defined(__ANDROID__) || defined(ANDROID)
#define AGS_PLATFORM_SCUMMVM (0)
#define AGS_PLATFORM_OS_WINDOWS (0)
#define AGS_PLATFORM_OS_LINUX (0)
#define AGS_PLATFORM_OS_MACOS (0)
#define AGS_PLATFORM_OS_ANDROID (1)
#define AGS_PLATFORM_OS_IOS (0)
#define AGS_PLATFORM_OS_PSP (0)
#elif defined(_WIN32)
//define something for Windows (32-bit and 64-bit)
#define AGS_PLATFORM_SCUMMVM (0)
#define AGS_PLATFORM_OS_WINDOWS (1)
#define AGS_PLATFORM_OS_LINUX (0)
#define AGS_PLATFORM_OS_MACOS (0)
#define AGS_PLATFORM_OS_ANDROID (0)
#define AGS_PLATFORM_OS_IOS (0)
#define AGS_PLATFORM_OS_PSP (0)
#elif defined(__APPLE__)
#define AGS_PLATFORM_SCUMMVM (0)
#include "ags/shared/ags/shared/TargetConditionals.h"
#ifndef TARGET_OS_SIMULATOR
#define TARGET_OS_SIMULATOR (0)
#endif
#ifndef TARGET_OS_IOS
#define TARGET_OS_IOS (0)
#endif
#ifndef TARGET_OS_OSX
#define TARGET_OS_OSX (0)
#endif
#if TARGET_OS_SIMULATOR || TARGET_IPHONE_SIMULATOR
#define AGS_PLATFORM_OS_WINDOWS (0)
#define AGS_PLATFORM_OS_LINUX (0)
#define AGS_PLATFORM_OS_MACOS (0)
#define AGS_PLATFORM_OS_ANDROID (0)
#define AGS_PLATFORM_OS_IOS (1)
#define AGS_PLATFORM_OS_PSP (0)
#elif TARGET_OS_IOS || TARGET_OS_IPHONE
#define AGS_PLATFORM_OS_WINDOWS (0)
#define AGS_PLATFORM_OS_LINUX (0)
#define AGS_PLATFORM_OS_MACOS (0)
#define AGS_PLATFORM_OS_ANDROID (0)
#define AGS_PLATFORM_OS_IOS (1)
#define AGS_PLATFORM_OS_PSP (0)
#elif TARGET_OS_OSX || TARGET_OS_MAC
#define AGS_PLATFORM_OS_WINDOWS (0)
#define AGS_PLATFORM_OS_LINUX (0)
#define AGS_PLATFORM_OS_MACOS (1)
#define AGS_PLATFORM_OS_ANDROID (0)
#define AGS_PLATFORM_OS_IOS (0)
#define AGS_PLATFORM_OS_PSP (0)
#else
#error "Unknown Apple platform"
#endif
#elif defined(__linux__)
#define AGS_PLATFORM_OS_WINDOWS (0)
#define AGS_PLATFORM_OS_LINUX (1)
#define AGS_PLATFORM_OS_MACOS (0)
#define AGS_PLATFORM_OS_ANDROID (0)
#define AGS_PLATFORM_OS_IOS (0)
#define AGS_PLATFORM_OS_PSP (0)
#else
#error "Unknown platform"
#endif
#if 0
#define AGS_PLATFORM_WINDOWS_MINGW (1)
#else
#define AGS_PLATFORM_WINDOWS_MINGW (0)
#endif
#if defined(__LP64__)
// LP64 machine, macOS or Linux
// int 32bit | long 64bit | long long 64bit | void* 64bit
#define AGS_PLATFORM_64BIT (1)
#elif defined(_WIN64)
// LLP64 machine, Windows
// int 32bit | long 32bit | long long 64bit | void* 64bit
#define AGS_PLATFORM_64BIT (1)
#else
// 32-bit machine, Windows or Linux or macOS
// int 32bit | long 32bit | long long 64bit | void* 32bit
#define AGS_PLATFORM_64BIT (0)
#endif
#if defined(SCUMM_LITTLE_ENDIAN)
#define AGS_PLATFORM_ENDIAN_LITTLE (1)
#define AGS_PLATFORM_ENDIAN_BIG (0)
#elif defined(SCUMM_BIG_ENDIAN)
#define AGS_PLATFORM_ENDIAN_LITTLE (0)
#define AGS_PLATFORM_ENDIAN_BIG (1)
#else
#error "No endianness defined"
#endif
#if defined(SCUMM_NEED_ALIGNMENT)
#define AGS_STRICT_ALIGNMENT
#endif
#define AGS_PLATFORM_DESKTOP ((AGS_PLATFORM_OS_WINDOWS) || (AGS_PLATFORM_OS_LINUX) || (AGS_PLATFORM_OS_MACOS))
#define AGS_PLATFORM_MOBILE ((AGS_PLATFORM_OS_ANDROID) || (AGS_PLATFORM_OS_IOS))
#define AGS_HAS_DIRECT3D (AGS_PLATFORM_OS_WINDOWS)
#define AGS_HAS_OPENGL ((AGS_PLATFORM_OS_WINDOWS) || (AGS_PLATFORM_OS_LINUX) || (AGS_PLATFORM_MOBILE))
#define AGS_OPENGL_ES2 (AGS_PLATFORM_OS_ANDROID)
// Only allow searching around for game data on desktop systems;
// otherwise use explicit argument either from program wrapper, command-line
// or read from default config.
#define AGS_SEARCH_FOR_GAME_ON_LAUNCH ((AGS_PLATFORM_OS_WINDOWS) || (AGS_PLATFORM_OS_LINUX) || (AGS_PLATFORM_OS_MACOS))
#if !defined(DEBUG_MANAGED_OBJECTS)
#define DEBUG_MANAGED_OBJECTS (0)
#endif
#if !defined(DEBUG_SPRITECACHE)
#define DEBUG_SPRITECACHE (0)
#endif
} // namespace AGS3
#endif

View File

@@ -0,0 +1,134 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// Basic types definition
//
//=============================================================================
#ifndef AGS_SHARED_CORE_TYPES_H
#define AGS_SHARED_CORE_TYPES_H
#include "common/scummsys.h"
#include "ags/lib/std.h"
namespace AGS3 {
#ifndef NULL
#define NULL nullptr
#endif
// Not all compilers have this. Added in clang and gcc followed
#ifndef __has_attribute
#define __has_attribute(x) 0
#endif
#ifndef FORCEINLINE
#ifdef _MSC_VER
#define FORCEINLINE __forceinline
#elif defined (__GNUC__) || __has_attribute(__always_inline__)
#define FORCEINLINE inline __attribute__((__always_inline__))
#else
#define FORCEINLINE inline
#endif
#endif
typedef uint8 uint8_t;
typedef uint16 uint16_t;
typedef uint32 uint32_t;
typedef uint64 uint64_t;
typedef int8 int8_t;
typedef int16 int16_t;
typedef int32 int32_t;
typedef int64 int64_t;
typedef int64 soff_t; // Stream offset type
typedef int64 intptr_t;
typedef uint64 uintptr_t;
// fixed point type
#define fixed_t int32_t
#define color_t int
#undef INT16_MIN
#undef INT16_MAX
#undef UINT16_MAX
#undef INT32_MIN
#undef INT32_MAX
#undef INT_MIN
#undef INT_MAX
#undef UINT32_MAX
#undef UINT_MAX
#undef SIZE_MAX
#define INT16_MIN -32768
#define INT16_MAX 0x7fff
#define UINT16_MAX 0xffff
#define INT32_MIN (-2147483647 - 1)
#define INT32_MAX 2147483647
#define INT_MIN (-2147483647 - 1)
#define INT_MAX 2147483647
#define UINT_MAX 0xffffffff
#define SIZE_MAX 0xffffffff
#define UINT32_MAX 0xffffffff
#undef TRUE
#undef FALSE
#define TRUE true
#define FALSE false
// TODO: use distinct fixed point class
enum {
kShift = 16,
kUnit = 1 << kShift
};
/**
* Basic class that can hold either a number or a pointer. Helps avoid some
* of the more nasty casts the codebase does, which was causing issues
* on 64-bit systems
*/
class NumberPtr {
intptr_t _value;
public:
NumberPtr() : _value(0) {
}
NumberPtr(int value) : _value(value) {
}
NumberPtr(void *ptr) : _value((intptr_t)ptr) {
}
NumberPtr(const void *ptr) : _value((intptr_t)ptr) {
}
operator int32_t() const {
return (int32_t)_value;
}
intptr_t full() const { return _value; }
void *ptr() const { return (void *)_value; }
const void *cptr() const { return (const void *)_value; }
};
} // namespace AGS3
#endif

View File

@@ -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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/debugging/debug_manager.h"
#include "ags/shared/util/string_types.h"
#include "ags/globals.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
DebugOutput::DebugOutput(const String &id, IOutputHandler *handler, MessageType def_verbosity, bool enabled)
: _id(id)
, _handler(handler)
, _enabled(enabled)
, _defaultVerbosity(def_verbosity) {
_groupFilter.resize(_GP(DbgMgr)._lastGroupID + 1, _defaultVerbosity);
}
String DebugOutput::GetID() const {
return _id;
}
IOutputHandler *DebugOutput::GetHandler() const {
return _handler;
}
bool DebugOutput::IsEnabled() const {
return _enabled;
}
void DebugOutput::SetEnabled(bool enable) {
_enabled = enable;
}
void DebugOutput::SetGroupFilter(DebugGroupID id, MessageType verbosity) {
uint32_t key = _GP(DbgMgr).GetGroup(id).UID.ID;
if (key != kDbgGroup_None)
_groupFilter[key] = verbosity;
else
_unresolvedGroups.insert(std::make_pair(id.SID, verbosity));
}
void DebugOutput::SetAllGroupFilters(MessageType verbosity) {
for (auto &group : _groupFilter)
group = verbosity;
for (auto &group : _unresolvedGroups)
group._value = verbosity;
}
void DebugOutput::ClearGroupFilters() {
for (auto &gf : _groupFilter)
gf = kDbgMsg_None;
_unresolvedGroups.clear();
}
void DebugOutput::ResolveGroupID(DebugGroupID id) {
if (!id.IsValid())
return;
DebugGroupID real_id = _GP(DbgMgr).GetGroup(id).UID;
if (real_id.IsValid()) {
if (_groupFilter.size() <= id.ID)
_groupFilter.resize(id.ID + 1, _defaultVerbosity);
GroupNameToMTMap::const_iterator it = _unresolvedGroups.find(real_id.SID);
if (it != _unresolvedGroups.end()) {
_groupFilter[real_id.ID] = it->_value;
_unresolvedGroups.erase(it);
}
}
}
bool DebugOutput::TestGroup(DebugGroupID id, MessageType mt) const {
DebugGroupID real_id = _GP(DbgMgr).GetGroup(id).UID;
if (real_id.ID == (uint32_t)kDbgGroup_None || real_id.ID >= _groupFilter.size())
return false;
return (_groupFilter[real_id.ID] >= mt) != 0;
}
DebugManager::DebugManager() {
// Add hardcoded groups
RegisterGroup(DebugGroup(DebugGroupID(kDbgGroup_Main, "main"), ""));
RegisterGroup(DebugGroup(DebugGroupID(kDbgGroup_Game, "game"), "Game"));
RegisterGroup(DebugGroup(DebugGroupID(kDbgGroup_Script, "script"), "Script"));
RegisterGroup(DebugGroup(DebugGroupID(kDbgGroup_SprCache, "sprcache"), "Sprite cache"));
RegisterGroup(DebugGroup(DebugGroupID(kDbgGroup_ManObj, "manobj"), "Managed obj"));
_firstFreeGroupID = _groups.size();
_lastGroupID = _firstFreeGroupID;
}
DebugGroup DebugManager::GetGroup(DebugGroupID id) {
if (id.ID != (uint32_t)kDbgGroup_None) {
return id.ID < _groups.size() ? _groups[id.ID] : DebugGroup();
} else if (!id.SID.IsEmpty()) {
GroupByStringMap::const_iterator it = _groupByStrLookup.find(id.SID);
return it != _groupByStrLookup.end() ? _groups[it->_value.ID] : DebugGroup();
}
return DebugGroup();
}
PDebugOutput DebugManager::GetOutput(const String &id) {
OutMap::const_iterator it = _outputs.find(id);
return it != _outputs.end() ? it->_value.Target : PDebugOutput();
}
DebugGroup DebugManager::RegisterGroup(const String &id, const String &out_name) {
DebugGroup group = GetGroup(id);
if (group.UID.IsValid())
return group;
group = DebugGroup(DebugGroupID(++_GP(DbgMgr)._lastGroupID, id), out_name);
_groups.push_back(group);
_groupByStrLookup[group.UID.SID] = group.UID;
// Resolve group reference on every output target
for (OutMap::const_iterator it = _outputs.begin(); it != _outputs.end(); ++it) {
it->_value.Target->ResolveGroupID(group.UID);
}
return group;
}
void DebugManager::RegisterGroup(const DebugGroup &group) {
if (_groups.size() <= group.UID.ID)
_groups.resize(group.UID.ID + 1);
_groups[group.UID.ID] = group; _groupByStrLookup[group.UID.SID] = group.UID;
}
PDebugOutput DebugManager::RegisterOutput(const String &id, IOutputHandler *handler, MessageType def_verbosity, bool enabled) {
_outputs[id].Target = PDebugOutput(new DebugOutput(id, handler, def_verbosity, enabled));
_outputs[id].Suppressed = false;
return _outputs[id].Target;
}
void DebugManager::UnregisterAll() {
_lastGroupID = _firstFreeGroupID;
_groups.clear();
_groupByStrLookup.clear();
_outputs.clear();
}
void DebugManager::UnregisterGroup(DebugGroupID id) {
DebugGroup group = GetGroup(id);
if (!group.UID.IsValid())
return;
_groups[group.UID.ID] = DebugGroup();
_groupByStrLookup.erase(group.UID.SID);
}
void DebugManager::UnregisterOutput(const String &id) {
_outputs.erase(id);
}
void DebugManager::Print(DebugGroupID group_id, MessageType mt, const String &text) {
const DebugGroup &group = GetGroup(group_id);
DebugMessage msg(text, group.UID.ID, group.OutputName, mt);
for (OutMap::iterator it = _outputs.begin(); it != _outputs.end(); ++it) {
SendMessage(it->_value, msg);
}
}
void DebugManager::SendMessage(const String &out_id, const DebugMessage &msg) {
OutMap::iterator it = _outputs.find(out_id);
if (it != _outputs.end())
SendMessage(it->_value, msg);
}
void DebugManager::SendMessage(OutputSlot &out, const DebugMessage &msg) {
IOutputHandler *handler = out.Target->GetHandler();
if (!handler || !out.Target->IsEnabled() || out.Suppressed)
return;
if (!out.Target->TestGroup(msg.GroupID, msg.MT))
return;
// We suppress current target before the call so that if it makes
// a call to output system itself, message would not print to the
// same target
out.Suppressed = true;
handler->PrintMessage(msg);
out.Suppressed = false;
}
namespace Debug {
void Printf(const String &text) {
_GP(DbgMgr).Print(kDbgGroup_Main, kDbgMsg_Default, text);
}
void Printf(MessageType mt, const String &text) {
_GP(DbgMgr).Print(kDbgGroup_Main, mt, text);
}
void Printf(DebugGroupID group, MessageType mt, const String &text) {
_GP(DbgMgr).Print(group, mt, text);
}
void Printf(const char *fmt, ...) {
va_list argptr;
va_start(argptr, fmt);
_GP(DbgMgr).Print(kDbgGroup_Main, kDbgMsg_Default, String::FromFormatV(fmt, argptr));
va_end(argptr);
}
void Printf(MessageType mt, const char *fmt, ...) {
va_list argptr;
va_start(argptr, fmt);
_GP(DbgMgr).Print(kDbgGroup_Main, mt, String::FromFormatV(fmt, argptr));
va_end(argptr);
}
void Printf(DebugGroupID group, MessageType mt, const char *fmt, ...) {
va_list argptr;
va_start(argptr, fmt);
_GP(DbgMgr).Print(group, mt, String::FromFormatV(fmt, argptr));
va_end(argptr);
}
} // namespace Debug
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,168 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// AGS logging system is built with idea that the engine components should not
// be bothered with specifying particular output method. Instead they use
// generic logging interface, and the actual message printing is done by one
// or more registered handlers.
// Firstly this makes logging functions independent of running platform or
// back-end, secondly it grants end-users ability to configure output according
// to their preference.
//
// To make the logging work we need to register two sets of "entities":
// debug groups and output targets.
// Debug group is an arbitrary object with a name that describes message
// sender.
// Output target defines printing handler and a set of verbosity rules
// one per each known group.
//
// When the message is sent, it is tagged with one of the existing group IDs
// and a message type (debug info, warning, error). This message is sent onto
// each of the registered output targets, which do checks to find out whether
// the message is permitted to be sent further to the printing handler, or not.
//
//=============================================================================
#ifndef AGS_SHARED_DEBUGGING_DEBUG_MANAGER_H
#define AGS_SHARED_DEBUGGING_DEBUG_MANAGER_H
#include "common/std/memory.h"
#include "common/std/map.h"
#include "ags/shared/debugging/out.h"
#include "ags/shared/debugging/output_handler.h"
#include "ags/shared/util/string.h"
#include "ags/shared/util/string_types.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
// DebugGroup is a message sender definition, identified by DebugGroupID
// and providing OutputName that could be used when printing its messages.
// OutputName may or may not be same as DebugGroupID.SID.
struct DebugGroup {
DebugGroupID UID;
String OutputName;
DebugGroup() {
}
DebugGroup(DebugGroupID id, String out_name) : UID(id), OutputName(out_name) {
}
};
// DebugOutput is a slot for IOutputHandler with its own group filter
class DebugOutput {
public:
DebugOutput(const String &id, IOutputHandler *handler, MessageType def_verbosity = kDbgMsg_All, bool enabled = true);
String GetID() const;
IOutputHandler *GetHandler() const;
bool IsEnabled() const;
void SetEnabled(bool enable);
// Setup group filter: either allow or disallow a group with the given ID
void SetGroupFilter(DebugGroupID id, MessageType verbosity);
// Assign same verbosity level to all known groups
void SetAllGroupFilters(MessageType verbosity);
// Clear all group filters; this efficiently disables everything
void ClearGroupFilters();
// Try to resolve group filter unknown IDs
void ResolveGroupID(DebugGroupID id);
// Test if given group id is permitted
bool TestGroup(DebugGroupID id, MessageType mt) const;
private:
String _id;
IOutputHandler *_handler;
bool _enabled;
MessageType _defaultVerbosity;
// Set of permitted groups' numeric IDs
std::vector<MessageType> _groupFilter;
// Set of unresolved groups, which numeric IDs are not yet known
typedef std::unordered_map<String, MessageType, IgnoreCase_Hash, IgnoreCase_EqualTo> GroupNameToMTMap;
GroupNameToMTMap _unresolvedGroups;
};
typedef std::shared_ptr<DebugOutput> PDebugOutput;
class DebugManager {
friend class DebugOutput;
public:
DebugManager();
// Gets full group ID for any partial one; if the group is not registered returns unset ID
DebugGroup GetGroup(DebugGroupID id);
// Gets output control interface for the given ID
PDebugOutput GetOutput(const String &id);
// Registers debugging group with the given string ID; numeric ID
// will be assigned internally. Returns full ID pair.
// If the group with such string id already exists, returns existing ID.
DebugGroup RegisterGroup(const String &id, const String &out_name);
// Registers output delegate for passing debug messages to;
// if the output with such id already exists, replaces the old one
PDebugOutput RegisterOutput(const String &id, IOutputHandler *handler, MessageType def_verbosity = kDbgMsg_All, bool enabled = true);
// Unregisters all groups and all targets
void UnregisterAll();
// Unregisters debugging group with the given ID
void UnregisterGroup(DebugGroupID id);
// Unregisters output delegate with the given ID
void UnregisterOutput(const String &id);
// Output message of given group and message type
void Print(DebugGroupID group_id, MessageType mt, const String &text);
// Send message directly to the output with given id; the message
// must pass the output's message filter though
void SendMessage(const String &out_id, const DebugMessage &msg);
private:
// OutputSlot struct wraps over output target and adds a flag which indicates
// that this target is temporarily disabled (for internal use only)
struct OutputSlot {
PDebugOutput Target;
bool Suppressed;
OutputSlot() : Suppressed(false) {
}
};
typedef std::vector<DebugGroup> GroupVector;
typedef std::unordered_map<String, DebugGroupID, IgnoreCase_Hash, IgnoreCase_EqualTo> GroupByStringMap;
typedef std::unordered_map<String, OutputSlot, IgnoreCase_Hash, IgnoreCase_EqualTo> OutMap;
void RegisterGroup(const DebugGroup &id);
void SendMessage(OutputSlot &out, const DebugMessage &msg);
uint32_t _firstFreeGroupID;
uint32_t _lastGroupID;
GroupVector _groups;
GroupByStringMap _groupByStrLookup;
OutMap _outputs;
};
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,173 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// Debug output interface provides functions which send a formatted message
// tagged with group ID and message type to the registered output handlers.
//
// Depending on configuration this message may be printed by any of those
// handlers, or none of them. The calling unit should not worry about where the
// message goes.
//
//-----------------------------------------------------------------------------
//
// On using message types.
//
// Please keep in mind, that there are different levels of errors. AGS logging
// system allows to classify debug message by two parameters: debug group and
// message type. Sometimes message type alone is not enough. Debug groups can
// also be used to distinct messages that has less (or higher) importance.
//
// For example, there are engine errors and user (game-dev) mistakes. Script
// commands that cannot be executed under given circumstances are user
// mistakes, and usually are not as severe as internal engine errors. This is
// why it is advised to use a separate debug group for mistakes like that to
// distinct them from reports on the internal engine's problems and make
// verbosity configuration flexible.
//
// kDbgMsg_Debug - is the most mundane type of message (if the printing function
// argument list does not specify message type, it is probably kDbgMsg_Debug).
// You can use it for almost anything, from noting some process steps to
// displaying current object state. If certain messages are meant to be printed
// very often, consider using another distinct debug group so that it may be
// disabled to reduce log verbosity.
//
// kDbgMsg_Info - is a type for important notifications, such as initialization
// and stopping of engine components.
//
// kDbgMsg_Warn - this is suggested for more significant cases, when you find
// out that something is not right, but is not immediately affecting engine
// processing. For example: certain object was constructed in a way that
// would make them behave unexpectedly, or certain common files are missing but
// it is not clear yet whether the game will be accessing them.
// In other words: use kDbgMsg_Warn when there is no problem right away, but
// you are *anticipating* that one may happen under certain circumstances.
//
// kDbgMsg_Error - use this kind of message is for actual serious problems.
// If certain operation assumes both positive and negative results are
// acceptable, it is preferred to report such negative result with simple
// kDbgMsg_Debug message. kDbgMsg_Error is for negative results that are not
// considered acceptable for normal run.
//
// kDbgMsg_Fatal - is the message type to be reported when the program or
// component abortion is imminent.
//
//=============================================================================
#ifndef AGS_SHARED_CORE_DEBUGGING_OUT_H
#define AGS_SHARED_CORE_DEBUGGING_OUT_H
#include "ags/shared/util/string.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
// Message types provide distinction for debug messages by their intent.
enum MessageType {
kDbgMsg_None = 0,
// Alerts may be informative messages with topmost level of importance,
// such as reporting engine startup and shutdown.
kDbgMsg_Alert,
// Fatal errors are ones that make program abort immediately.
kDbgMsg_Fatal,
// Error messages are about engine not being able to perform requested
// operation in a situation when that will affect game playability and
// further execution.
kDbgMsg_Error,
// Warnings are made when unexpected or non-standart behavior
// is detected in program, which is not immediately critical,
// but may be a symptom of a bigger problem.
kDbgMsg_Warn,
// General information messages.
kDbgMsg_Info,
// Debug reason is for arbitrary information about events and current
// game state.
kDbgMsg_Debug,
// Convenient aliases
kDbgMsg_Default = kDbgMsg_Debug,
kDbgMsg_All = kDbgMsg_Debug
};
// This enumeration is a list of common hard-coded groups, but more could
// be added via debugging configuration interface (see 'debug/debug.h').
enum CommonDebugGroup : uint32 {
kDbgGroup_None = UINT32_MAX,
// Main debug group is for reporting general engine status and issues
kDbgGroup_Main = 0,
// Game group is for logging game logic state and issues
kDbgGroup_Game,
// Log from the game script
kDbgGroup_Script,
// Sprite cache logging
kDbgGroup_SprCache,
// Group for debugging managed object state (can slow engine down!)
kDbgGroup_ManObj
};
// Debug group identifier defining either numeric or string id, or both
struct DebugGroupID {
uint32_t ID;
String SID;
DebugGroupID() : ID((uint32_t)kDbgGroup_None) {
}
DebugGroupID(uint32_t id, const String &sid = "") : ID(id), SID(sid) {
}
DebugGroupID(const String &sid) : ID((uint32_t)kDbgGroup_None), SID(sid) {
}
// Tells if any of the id components is valid
bool IsValid() const {
return ID != (uint32_t)kDbgGroup_None || !SID.IsEmpty();
}
// Tells if both id components are properly set
bool IsComplete() const {
return ID != (uint32_t)kDbgGroup_None && !SID.IsEmpty();
}
};
namespace Debug {
//
// Debug output
//
// Output a plain message of default group and default type
void Printf(const String &text);
// Output a plain message of default group and given type
void Printf(MessageType mt, const String &text);
// Output a plain message of given group and type
void Printf(DebugGroupID group_id, MessageType mt, const String &text);
// Output formatted message of default group and default type
void Printf(const char *fmt, ...);
// Output formatted message of default group and given type
void Printf(MessageType mt, const char *fmt, ...);
// Output formatted message of given group and type
void Printf(DebugGroupID group_id, MessageType mt, const char *fmt, ...);
} // namespace Debug
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,68 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// IOutputHandler is a debug printing interface. Its implementations can be
// registered as potential output for the debug log.
//
//=============================================================================
#ifndef AGS_SHARED_CORE_DEBUGGING_OUTPUT_HANDLER_H
#define AGS_SHARED_CORE_DEBUGGING_OUTPUT_HANDLER_H
#include "ags/shared/debugging/out.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
struct DebugMessage {
String Text;
uint32_t GroupID;
String GroupName;
MessageType MT;
DebugMessage() : GroupID((uint32_t)kDbgGroup_None), MT(kDbgMsg_None) {
}
DebugMessage(const String &text, uint32_t group_id, const String &group_name, MessageType mt)
: Text(text)
, GroupID(group_id)
, GroupName(group_name)
, MT(mt) {
}
};
class IOutputHandler {
public:
virtual ~IOutputHandler() {}
// Print the given text sent from the debug group.
// Implementations are free to decide which message components are to be printed, and how.
virtual void PrintMessage(const DebugMessage &msg) = 0;
};
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,126 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_FONT_AGS_FONT_RENDERER_H
#define AGS_SHARED_FONT_AGS_FONT_RENDERER_H
#include "common/std/utility.h"
#include "ags/shared/core/types.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
class BITMAP;
class IAGSFontRenderer {
public:
virtual bool LoadFromDisk(int fontNumber, int fontSize) = 0;
virtual void FreeMemory(int fontNumber) = 0;
virtual bool SupportsExtendedCharacters(int fontNumber) = 0;
virtual int GetTextWidth(const char *text, int fontNumber) = 0;
// Get actual height of the given line of text
virtual int GetTextHeight(const char *text, int fontNumber) = 0;
virtual void RenderText(const char *text, int fontNumber, BITMAP *destination, int x, int y, int colour) = 0;
virtual void AdjustYCoordinateForFont(int *ycoord, int fontNumber) = 0;
virtual void EnsureTextValidForFont(char *text, int fontNumber) = 0;
protected:
IAGSFontRenderer() {}
~IAGSFontRenderer() {}
};
// Extended font renderer interface.
// WARNING: this interface is exposed for plugins and declared for the second time in agsplugin.h
class IAGSFontRenderer2 : public IAGSFontRenderer {
public:
// Returns engine API version this font renderer complies to.
// Must not be lower than 26 (this interface was added at API v26).
virtual int GetVersion() = 0;
// Returns an arbitrary renderer name; this is for informational
// purposes only.
virtual const char *GetRendererName() = 0;
// Returns given font's name (if available).
virtual const char *GetFontName(int fontNumber) = 0;
// Returns the given font's height: that is the maximal vertical size
// that the font glyphs may occupy.
virtual int GetFontHeight(int fontNumber) = 0;
// Returns the given font's linespacing;
// is allowed to return 0, telling that no specific linespacing
// is assigned for this font.
virtual int GetLineSpacing(int fontNumber) = 0;
protected:
IAGSFontRenderer2() {}
~IAGSFontRenderer2() {}
};
// Font render params, mainly for dealing with various compatibility issues.
struct FontRenderParams {
// Font's render multiplier
int SizeMultiplier = 1;
int LoadMode = 0; // contains font flags from FFLG_LOADMODEMASK
};
// Describes loaded font's properties
struct FontMetrics {
// Nominal font's height, equals to the game-requested size of the font.
// This may or not be equal to font's face height; sometimes a font cannot
// be scaled exactly to particular size, and then nominal height appears different
// (usually - smaller) than the real one.
int NominalHeight = 0;
// Real font's height, equals to reported ascender + descender.
// This is what you normally think as a font's height.
int RealHeight = 0;
// Compatible height, equals to either NominalHeight or RealHeight,
// selected depending on the game settings.
// This property is used in calculating linespace, etc.
int CompatHeight = 0;
// Maximal vertical extent of a font (top; bottom), this tells the actual
// graphical bounds that may be occupied by font's glyphs.
// In a "proper" font this extent is (0; RealHeight-1), but "bad" fonts may
// have individual glyphs exceeding these bounds, in both directions.
// Note that "top" may be negative!
std::pair<int, int> VExtent;
inline int ExtentHeight() const { return VExtent.second - VExtent.first; }
};
// The strictly internal font renderer interface, not to use in plugin API.
// Contains methods necessary for built-in font renderers.
class IAGSFontRendererInternal : public IAGSFontRenderer2 {
public:
// Tells if this is a bitmap font (otherwise it's a vector font)
virtual bool IsBitmapFont() = 0;
// Load font, applying extended font rendering parameters
virtual bool LoadFromDiskEx(int fontNumber, int fontSize, AGS::Shared::String *src_filename,
const FontRenderParams *params, FontMetrics *metrics) = 0;
// Fill FontMetrics struct; note that it may be left cleared if this is not supported
virtual void GetFontMetrics(int fontNumber, FontMetrics *metrics) = 0;
// Perform any necessary adjustments when the AA mode is toggled
virtual void AdjustFontForAntiAlias(int fontNumber, bool aa_mode) = 0;
protected:
IAGSFontRendererInternal() {}
~IAGSFontRendererInternal() {}
};
} // namespace AGS3
#endif

View File

@@ -0,0 +1,568 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/std/algorithm.h"
#include "ags/lib/alfont/alfont.h"
#include "common/std/vector.h"
#include "ags/shared/ac/common.h" // set_our_eip
#include "ags/shared/ac/game_struct_defines.h"
#include "ags/shared/debugging/out.h"
#include "ags/shared/font/fonts.h"
#include "ags/shared/font/ttf_font_renderer.h"
#include "ags/shared/font/wfn_font_renderer.h"
#include "ags/shared/gfx/bitmap.h"
#include "ags/shared/gui/gui_defines.h" // MAXLINE
#include "ags/shared/util/string_utils.h"
#include "ags/globals.h"
namespace AGS3 {
using namespace AGS::Shared;
FontInfo::FontInfo()
: Flags(0)
, Size(0)
, SizeMultiplier(1)
, Outline(FONT_OUTLINE_NONE)
, YOffset(0)
, LineSpacing(0)
, AutoOutlineStyle(kSquared)
, AutoOutlineThickness(0) {
}
void init_font_renderer() {
alfont_init();
alfont_text_mode(-1);
}
void shutdown_font_renderer() {
set_our_eip(9919);
alfont_exit();
}
void adjust_y_coordinate_for_text(int *ypos, size_t fontnum) {
if (fontnum >= _GP(fonts).size() || !_GP(fonts)[fontnum].Renderer)
return;
_GP(fonts)[fontnum].Renderer->AdjustYCoordinateForFont(ypos, fontnum);
}
bool font_first_renderer_loaded() {
return _GP(fonts).size() > 0 && _GP(fonts)[0].Renderer != nullptr;
}
bool is_font_loaded(size_t fontNumber) {
return fontNumber < _GP(fonts).size() && _GP(fonts)[fontNumber].Renderer != nullptr;
}
// Finish font's initialization
static void font_post_init(size_t fontNumber) {
Font &font = _GP(fonts)[fontNumber];
// If no font height property was provided, then try several methods,
// depending on which interface is available
if (font.Metrics.NominalHeight == 0 && font.Renderer) {
int height = 0;
if (font.Renderer2)
height = font.Renderer2->GetFontHeight(fontNumber);
if (height <= 0) {
// With the old renderer we have to rely on GetTextHeight;
// the implementations of GetTextHeight are allowed to return varied
// results depending on the text parameter.
// We use special line of text to get more or less reliable font height.
const char *height_test_string = "ZHwypgfjqhkilIK";
height = font.Renderer->GetTextHeight(height_test_string, fontNumber);
}
font.Metrics.NominalHeight = std::max(0, height);
font.Metrics.RealHeight = font.Metrics.NominalHeight;
font.Metrics.VExtent = std::make_pair(0, font.Metrics.RealHeight);
}
// Use either nominal or real pixel height to define font's logical height
// and default linespacing; logical height = nominal height is compatible with the old games
font.Metrics.CompatHeight = (font.Info.Flags & FFLG_REPORTNOMINALHEIGHT) != 0 ?
font.Metrics.NominalHeight : font.Metrics.RealHeight;
if (font.Info.Outline != FONT_OUTLINE_AUTO) {
font.Info.AutoOutlineThickness = 0;
}
// If no linespacing property was provided, then try several methods,
// depending on which interface is available
font.LineSpacingCalc = font.Info.LineSpacing;
if (font.Info.LineSpacing == 0) {
int linespacing = 0;
if (font.Renderer2)
linespacing = font.Renderer2->GetLineSpacing(fontNumber);
if (linespacing > 0) {
font.LineSpacingCalc = linespacing;
} else {
// Calculate default linespacing from the font height + outline thickness.
font.Info.Flags |= FFLG_DEFLINESPACING;
font.LineSpacingCalc = font.Metrics.CompatHeight + 2 * font.Info.AutoOutlineThickness;
}
}
}
static void font_replace_renderer(size_t fontNumber, IAGSFontRenderer* renderer, IAGSFontRenderer2* renderer2) {
_GP(fonts)[fontNumber].Renderer = renderer;
_GP(fonts)[fontNumber].Renderer2 = renderer2;
// If this is one of our built-in font renderers, then correctly
// reinitialize interfaces and font metrics
if ((renderer == &_GP(ttfRenderer)) || (renderer == &_GP(wfnRenderer))) {
_GP(fonts)[fontNumber].RendererInt = static_cast<IAGSFontRendererInternal*>(renderer);
_GP(fonts)[fontNumber].RendererInt->GetFontMetrics(fontNumber, &_GP(fonts)[fontNumber].Metrics);
} else {
// Otherwise, this is probably coming from plugin
_GP(fonts)[fontNumber].RendererInt = nullptr;
_GP(fonts)[fontNumber].Metrics = FontMetrics(); // reset to defaults
}
font_post_init(fontNumber);
}
IAGSFontRenderer *font_replace_renderer(size_t fontNumber, IAGSFontRenderer *renderer) {
if (fontNumber >= _GP(fonts).size())
return nullptr;
IAGSFontRenderer* old_render = _GP(fonts)[fontNumber].Renderer;
font_replace_renderer(fontNumber, renderer, nullptr);
return old_render;
}
IAGSFontRenderer *font_replace_renderer(size_t fontNumber, IAGSFontRenderer2 *renderer) {
if (fontNumber >= _GP(fonts).size())
return nullptr;
IAGSFontRenderer* old_render = _GP(fonts)[fontNumber].Renderer;
font_replace_renderer(fontNumber, renderer, renderer);
return old_render;
}
void font_recalc_metrics(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size())
return;
_GP(fonts)[fontNumber].Metrics = FontMetrics();
font_post_init(fontNumber);
}
bool is_bitmap_font(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].RendererInt)
return false;
return _GP(fonts)[fontNumber].RendererInt->IsBitmapFont();
}
bool font_supports_extended_characters(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].Renderer)
return false;
return _GP(fonts)[fontNumber].Renderer->SupportsExtendedCharacters(fontNumber);
}
const char *get_font_name(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].Renderer2)
return "";
const char *name = _GP(fonts)[fontNumber].Renderer2->GetFontName(fontNumber);
return name ? name : "";
}
int get_font_flags(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size())
return 0;
return _GP(fonts)[fontNumber].Info.Flags;
}
void ensure_text_valid_for_font(char *text, size_t fontnum) {
if (fontnum >= _GP(fonts).size() || !_GP(fonts)[fontnum].Renderer)
return;
_GP(fonts)[fontnum].Renderer->EnsureTextValidForFont(text, fontnum);
}
int get_font_scaling_mul(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].Renderer)
return 0;
return _GP(fonts)[fontNumber].Info.SizeMultiplier;
}
int get_text_width(const char *texx, size_t fontNumber) {
if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].Renderer)
return 0;
return _GP(fonts)[fontNumber].Renderer->GetTextWidth(texx, fontNumber);
}
int get_text_width_outlined(const char *text, size_t font_number) {
if (font_number >= _GP(fonts).size() || !_GP(fonts)[font_number].Renderer)
return 0;
if (text == nullptr || text[0] == 0) // we ignore outline width since the text is empty
return 0;
int self_width = _GP(fonts)[font_number].Renderer->GetTextWidth(text, font_number);
int outline = _GP(fonts)[font_number].Info.Outline;
if (outline < 0 || static_cast<size_t>(outline) > _GP(fonts).size()) { // FONT_OUTLINE_AUTO or FONT_OUTLINE_NONE
return self_width + 2 * _GP(fonts)[font_number].Info.AutoOutlineThickness;
}
int outline_width = _GP(fonts)[outline].Renderer->GetTextWidth(text, outline);
return MAX(self_width, outline_width);
}
int get_text_height(const char *text, size_t font_number) {
if (font_number >= _GP(fonts).size() || !_GP(fonts)[font_number].Renderer)
return 0;
return _GP(fonts)[font_number].Renderer->GetTextHeight(text, font_number);
}
int get_font_outline(size_t font_number) {
if (font_number >= _GP(fonts).size())
return FONT_OUTLINE_NONE;
return _GP(fonts)[font_number].Info.Outline;
}
int get_font_outline_thickness(size_t font_number) {
if (font_number >= _GP(fonts).size())
return 0;
return _GP(fonts)[font_number].Info.AutoOutlineThickness;
}
void set_font_outline(size_t font_number, int outline_type,
enum FontInfo::AutoOutlineStyle style, int thickness) {
if (font_number >= _GP(fonts).size())
return;
_GP(fonts)[font_number].Info.Outline = outline_type;
_GP(fonts)[font_number].Info.AutoOutlineStyle = style;
_GP(fonts)[font_number].Info.AutoOutlineThickness = thickness;
}
bool is_font_antialiased(size_t font_number) {
if (font_number >= _GP(fonts).size())
return false;
return ShouldAntiAliasText() && !is_bitmap_font(font_number);
}
int get_font_height(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].Renderer)
return 0;
return _GP(fonts)[fontNumber].Metrics.CompatHeight;
}
int get_font_height_outlined(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].Renderer)
return 0;
int self_height = _GP(fonts)[fontNumber].Metrics.CompatHeight;
int outline = _GP(fonts)[fontNumber].Info.Outline;
if (outline < 0 || static_cast<size_t>(outline) > _GP(fonts).size()) { // FONT_OUTLINE_AUTO or FONT_OUTLINE_NONE
return self_height + 2 * _GP(fonts)[fontNumber].Info.AutoOutlineThickness;
}
int outline_height = _GP(fonts)[outline].Metrics.CompatHeight;
return MAX(self_height, outline_height);
}
int get_font_surface_height(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].Renderer)
return 0;
return _GP(fonts)[fontNumber].Metrics.ExtentHeight();
}
std::pair<int, int> get_font_surface_extent(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].Renderer)
return std::make_pair(0, 0);
return _GP(fonts)[fontNumber].Metrics.VExtent;
}
int get_font_linespacing(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size())
return 0;
return _GP(fonts)[fontNumber].LineSpacingCalc;
}
void set_font_linespacing(size_t fontNumber, int spacing) {
if (fontNumber < _GP(fonts).size()) {
_GP(fonts)[fontNumber].Info.Flags &= ~FFLG_DEFLINESPACING;
_GP(fonts)[fontNumber].Info.LineSpacing = spacing;
_GP(fonts)[fontNumber].LineSpacingCalc = spacing;
}
}
int get_text_lines_height(size_t fontNumber, size_t numlines) {
if (fontNumber >= _GP(fonts).size() || numlines == 0)
return 0;
return _GP(fonts)[fontNumber].LineSpacingCalc * (numlines - 1) +
(_GP(fonts)[fontNumber].Metrics.CompatHeight +
2 * _GP(fonts)[fontNumber].Info.AutoOutlineThickness);
}
int get_text_lines_surf_height(size_t fontNumber, size_t numlines) {
if (fontNumber >= _GP(fonts).size() || numlines == 0)
return 0;
return _GP(fonts)[fontNumber].LineSpacingCalc * (numlines - 1) +
(_GP(fonts)[fontNumber].Metrics.RealHeight +
2 * _GP(fonts)[fontNumber].Info.AutoOutlineThickness);
}
// Replaces AGS-specific linebreak tags with common '\n'
void unescape_script_string(const char *cstr, std::vector<char> &out) {
out.clear();
// Handle the special case of the first char
if (cstr[0] == '[') {
out.push_back('\n');
cstr++;
}
// Replace all other occurrences as they're found
// NOTE: we do not need to decode utf8 here, because
// we are only searching for low-code ascii chars.
const char *off;
for (off = cstr; *off; ++off) {
if (*off != '[') continue;
if (*(off - 1) == '\\') {
// convert \[ into [
out.insert(out.end(), cstr, off - 1);
out.push_back('[');
} else {
// convert [ into \n
out.insert(out.end(), cstr, off);
out.push_back('\n');
}
cstr = off + 1;
}
out.insert(out.end(), cstr, off + 1);
}
// Break up the text into lines
size_t split_lines(const char *todis, SplitLines &lines, int wii, int fonnt, size_t max_lines) {
// NOTE: following hack accommodates for the legacy math mistake in split_lines.
// It's hard to tell how crucial it is for the game looks, so research may be needed.
// TODO: IMHO this should rely not on game format, but script API level, because it
// defines necessary adjustments to game scripts. If you want to fix this, find a way to
// pass this flag here all the way from game.options[OPT_BASESCRIPTAPI] (or game format).
//
// if (game.options[OPT_BASESCRIPTAPI] < $Your current version$)
wii -= 1;
lines.Reset();
unescape_script_string(todis, lines.LineBuf);
char *theline = &lines.LineBuf.front();
char *scan_ptr = theline;
char *prev_ptr = theline;
char *last_whitespace = nullptr;
while (1) {
char *split_at = nullptr;
if (*scan_ptr == 0) {
// end of the text, add the last line if necessary
if (scan_ptr > theline) {
lines.Add(theline);
}
break;
}
if (*scan_ptr == ' ')
last_whitespace = scan_ptr;
// force end of line with the \n character
if (*scan_ptr == '\n') {
split_at = scan_ptr;
// otherwise, see if we are too wide
} else {
// temporarily terminate the line in the *next* char and test its width
char *next_ptr = scan_ptr;
ugetx(&next_ptr);
const int next_chwas = ugetc(next_ptr);
*next_ptr = 0;
if (get_text_width_outlined(theline, fonnt) > wii) {
// line is too wide, order the split
if (last_whitespace)
// revert to the last whitespace
split_at = last_whitespace;
else
// single very wide word, display as much as possible
split_at = prev_ptr;
}
// restore the character that was there before
usetc(next_ptr, next_chwas);
}
if (split_at == nullptr) {
prev_ptr = scan_ptr;
ugetx(&scan_ptr);
} else {
// check if even one char cannot fit...
if (split_at == theline && !((*theline == ' ') || (*theline == '\n'))) {
// cannot split with current width restriction
lines.Reset();
break;
}
// add this line; do the temporary terminator trick again
const int next_chwas = ugetc(split_at);
*split_at = 0;
lines.Add(theline);
usetc(split_at, next_chwas);
// check if too many lines
if (lines.Count() >= max_lines) {
lines[lines.Count() - 1].Append("...");
break;
}
// the next line starts from the split point
theline = split_at;
// skip the space or new line that caused the line break
if ((*theline == ' ') || (*theline == '\n'))
theline++;
scan_ptr = theline;
prev_ptr = theline;
last_whitespace = nullptr;
}
}
return lines.Count();
}
void wouttextxy(Shared::Bitmap *ds, int xxx, int yyy, size_t fontNumber, color_t text_color, const char *texx) {
if (fontNumber >= _GP(fonts).size())
return;
yyy += _GP(fonts)[fontNumber].Info.YOffset;
if (yyy > ds->GetClip().Bottom)
return; // each char is clipped but this speeds it up
if (_GP(fonts)[fontNumber].Renderer != nullptr) {
if (text_color == makeacol32(255, 0, 255, 255)) { // transparent color (magenta)
// WORKAROUND: Some Allegro routines are not implemented and alfont treats some magenta texts as invisible
// even if the alpha channel is fully opaque
// Slightly change the value if the game uses that color for fonts, so that they don't turn invisible
debug(0, "Overriding transparent text color!");
text_color--;
}
_GP(fonts)[fontNumber].Renderer->RenderText(texx, fontNumber, (BITMAP *)ds->GetAllegroBitmap(), xxx, yyy, text_color);
}
}
void set_fontinfo(size_t fontNumber, const FontInfo &finfo) {
if (fontNumber < _GP(fonts).size() && _GP(fonts)[fontNumber].Renderer) {
_GP(fonts)[fontNumber].Info = finfo;
font_post_init(fontNumber);
}
}
FontInfo get_fontinfo(size_t font_number) {
if (font_number < _GP(fonts).size())
return _GP(fonts)[font_number].Info;
return FontInfo();
}
// Loads a font from disk
bool load_font_size(size_t fontNumber, const FontInfo &font_info) {
if (_GP(fonts).size() <= fontNumber)
_GP(fonts).resize(fontNumber + 1);
else
wfreefont(fontNumber);
FontRenderParams params;
params.SizeMultiplier = font_info.SizeMultiplier;
params.LoadMode = (font_info.Flags & FFLG_LOADMODEMASK);
FontMetrics metrics;
Font &font = _GP(fonts)[fontNumber];
String src_filename;
if (_GP(ttfRenderer).LoadFromDiskEx(fontNumber, font_info.Size, &src_filename, &params, &metrics)) {
font.Renderer = &_GP(ttfRenderer);
font.Renderer2 = &_GP(ttfRenderer);
font.RendererInt = &_GP(ttfRenderer);
} else if (_GP(wfnRenderer).LoadFromDiskEx(fontNumber, font_info.Size, &src_filename, &params, &metrics)) {
font.Renderer = &_GP(wfnRenderer);
font.Renderer2 = &_GP(wfnRenderer);
font.RendererInt = &_GP(wfnRenderer);
}
if (!font.Renderer)
return false;
font.Info = font_info;
font.Metrics = metrics;
font_post_init(fontNumber);
Debug::Printf("Loaded font %d: %s, req size: %d; nominal h: %d, real h: %d, extent: %d,%d",
fontNumber, src_filename.GetCStr(), font_info.Size, font.Metrics.NominalHeight, font.Metrics.RealHeight,
font.Metrics.VExtent.first, font.Metrics.VExtent.second);
return true;
}
void wgtprintf(Shared::Bitmap *ds, int xxx, int yyy, size_t fontNumber, color_t text_color, char *fmt, ...) {
if (fontNumber >= _GP(fonts).size())
return;
char tbuffer[2000];
va_list ap;
va_start(ap, fmt);
vsnprintf(tbuffer, sizeof(tbuffer), fmt, ap);
va_end(ap);
wouttextxy(ds, xxx, yyy, fontNumber, text_color, tbuffer);
}
void alloc_font_outline_buffers(size_t font_number,
Bitmap **text_stencil, Bitmap **outline_stencil,
int text_width, int text_height, int color_depth) {
if (font_number >= _GP(fonts).size())
return;
Font &f = _GP(fonts)[font_number];
const int thick = 2 * f.Info.AutoOutlineThickness;
if (f.TextStencil.IsNull() || (f.TextStencil.GetColorDepth() != color_depth) ||
(f.TextStencil.GetWidth() < text_width) || (f.TextStencil.GetHeight() < text_height)) {
int sw = f.TextStencil.IsNull() ? 0 : f.TextStencil.GetWidth();
int sh = f.TextStencil.IsNull() ? 0 : f.TextStencil.GetHeight();
sw = MAX(text_width, sw);
sh = MAX(text_height, sh);
f.TextStencil.Create(sw, sh, color_depth);
f.OutlineStencil.Create(sw, sh + thick, color_depth);
f.TextStencilSub.CreateSubBitmap(&f.TextStencil, RectWH(Size(text_width, text_height)));
f.OutlineStencilSub.CreateSubBitmap(&f.OutlineStencil, RectWH(Size(text_width, text_height + thick)));
} else {
f.TextStencilSub.ResizeSubBitmap(text_width, text_height);
f.OutlineStencilSub.ResizeSubBitmap(text_width, text_height + thick);
}
*text_stencil = &f.TextStencilSub;
*outline_stencil = &f.OutlineStencilSub;
}
void adjust_fonts_for_render_mode(bool aa_mode) {
for (size_t i = 0; i < _GP(fonts).size(); ++i) {
if (_GP(fonts)[i].RendererInt != nullptr)
_GP(fonts)[i].RendererInt->AdjustFontForAntiAlias(i, aa_mode);
}
}
void wfreefont(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size())
return;
_GP(fonts)[fontNumber].TextStencilSub.Destroy();
_GP(fonts)[fontNumber].OutlineStencilSub.Destroy();
_GP(fonts)[fontNumber].TextStencil.Destroy();
_GP(fonts)[fontNumber].OutlineStencil.Destroy();
if (_GP(fonts)[fontNumber].Renderer != nullptr)
_GP(fonts)[fontNumber].Renderer->FreeMemory(fontNumber);
_GP(fonts)[fontNumber].Renderer = nullptr;
}
void free_all_fonts() {
for (size_t i = 0; i < _GP(fonts).size(); ++i) {
if (_GP(fonts)[i].Renderer != nullptr)
_GP(fonts)[i].Renderer->FreeMemory(i);
}
_GP(fonts).clear();
}
} // namespace AGS3

View File

@@ -0,0 +1,194 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_FONT_FONTS_H
#define AGS_SHARED_FONT_FONTS_H
#include "common/std/vector.h"
#include "ags/shared/ac/game_struct_defines.h"
#include "ags/shared/util/string.h"
#include "ags/shared/ac/game_struct_defines.h"
#include "ags/shared/font/ags_font_renderer.h"
#include "ags/shared/gfx/allegro_bitmap.h"
namespace AGS3 {
class IAGSFontRenderer;
class IAGSFontRenderer2;
class IAGSFontRendererInternal;
struct FontInfo;
struct FontRenderParams;
namespace AGS {
namespace Shared {
struct Font {
// Classic font renderer interface
IAGSFontRenderer *Renderer = nullptr;
// Extended font renderer interface (optional)
IAGSFontRenderer2 *Renderer2 = nullptr;
// Internal interface (only for built-in renderers)
IAGSFontRendererInternal *RendererInt = nullptr;
FontInfo Info;
// Values received from the renderer and saved for the reference
FontMetrics Metrics;
// Precalculated linespacing, based on font properties and compat settings
int LineSpacingCalc = 0;
// Outline buffers
Bitmap TextStencil, TextStencilSub;
Bitmap OutlineStencil, OutlineStencilSub;
Font() {}
};
} // namespace Shared
} // namespace AGS
using namespace AGS;
void init_font_renderer();
void shutdown_font_renderer();
void adjust_y_coordinate_for_text(int *ypos, size_t fontnum);
IAGSFontRenderer *font_replace_renderer(size_t fontNumber, IAGSFontRenderer *renderer);
IAGSFontRenderer *font_replace_renderer(size_t fontNumber, IAGSFontRenderer2 *renderer);
void font_recalc_metrics(size_t fontNumber);
bool font_first_renderer_loaded();
bool is_font_loaded(size_t fontNumber);
bool is_bitmap_font(size_t fontNumber);
bool font_supports_extended_characters(size_t fontNumber);
// Get font's name, if it's available, otherwise returns empty string
const char *get_font_name(size_t fontNumber);
// Get a collection of FFLG_* flags corresponding to this font
int get_font_flags(size_t fontNumber);
// TODO: with changes to WFN font renderer that implemented safe rendering of
// strings containing invalid chars (since 3.3.1) this function is not
// important, except for (maybe) few particular cases.
// Furthermore, its use complicated things, because AGS could modify some texts
// at random times (usually - drawing routines).
// Need to check whether it is safe to completely remove it.
void ensure_text_valid_for_font(char *text, size_t fontnum);
// Get font's scaling multiplier
int get_font_scaling_mul(size_t fontNumber);
// Calculate actual width of a line of text
int get_text_width(const char *texx, size_t fontNumber);
// Get the maximal width of the line of text, with corresponding outlining
int get_text_width_outlined(const char *text, size_t font_number);
// Get the maximal height of the line of text;
// note that this won't be a nominal font's height, but the max of each met glyph's graphical height.
int get_text_height(const char *text, size_t font_number);
// Get font's height; this value is used for logical arrangement of UI elements;
// note that this is a "formal" font height, that may have different value
// depending on compatibility mode (used when running old games);
int get_font_height(size_t fontNumber);
// Get the maximal height of the given font, with corresponding outlining
int get_font_height_outlined(size_t fontNumber);
// Get font's surface height: this always returns the height enough to accommodate
// font letters on a bitmap or a texture; the distinction is needed for compatibility reasons
int get_font_surface_height(size_t fontNumber);
// Get font's maximal graphical extent: this means the farthest vertical positions of glyphs,
// relative to the "pen" position. Besides letting to calculate the surface height,
// this information also lets to detect if some of the glyphs may appear above y0.
std::pair<int, int> get_font_surface_extent(size_t fontNumber);
// Get font's line spacing
int get_font_linespacing(size_t fontNumber);
// Set font's line spacing
void set_font_linespacing(size_t fontNumber, int spacing);
// Get font's outline type
int get_font_outline(size_t font_number);
// Get font's automatic outline thickness (if set)
int get_font_outline_thickness(size_t font_number);
// Gets the total maximal height of the given number of lines printed with the given font;
// note that this uses formal font height, for compatibility purposes
int get_text_lines_height(size_t fontNumber, size_t numlines);
// Gets the height of a graphic surface enough to accommodate this number of text lines;
// note this accounts for the real pixel font height
int get_text_lines_surf_height(size_t fontNumber, size_t numlines);
// Set font's outline type
void set_font_outline(size_t font_number, int outline_type,
enum FontInfo::AutoOutlineStyle style = FontInfo::kSquared, int thickness = 1);
bool is_font_antialiased(size_t font_number);
// Outputs a single line of text on the defined position on bitmap, using defined font, color and parameters
void wouttextxy(Shared::Bitmap *ds, int xxx, int yyy, size_t fontNumber, color_t text_color, const char *texx);
// Assigns FontInfo to the font
void set_fontinfo(size_t fontNumber, const FontInfo &finfo);
// Gets full information about the font
FontInfo get_fontinfo(size_t font_number);
// Loads a font from disk
bool load_font_size(size_t fontNumber, const FontInfo &font_info); void wgtprintf(Shared::Bitmap *ds, int xxx, int yyy, size_t fontNumber, color_t text_color, char *fmt, ...);
// Allocates two outline stencil buffers, or returns previously creates ones;
// these buffers are owned by the font, they should not be deleted by the caller.
void alloc_font_outline_buffers(size_t font_number,
Shared::Bitmap **text_stencil, Shared::Bitmap **outline_stencil,
int text_width, int text_height, int color_depth);
// Perform necessary adjustments on all fonts in case the text render mode changed (anti-aliasing etc)
void adjust_fonts_for_render_mode(bool aa_mode);
// Free particular font's data
void wfreefont(size_t fontNumber);
// Free all fonts data
void free_all_fonts();
// Tells if the text should be antialiased when possible
bool ShouldAntiAliasText();
// SplitLines class represents a list of lines and is meant to reduce
// subsequent memory (de)allocations if used often during game loops
// and drawing. For that reason it is not equivalent to std::vector,
// but keeps constructed String buffers intact for most time.
// TODO: implement proper strings pool.
class SplitLines {
public:
inline size_t Count() const {
return _count;
}
inline const Shared::String &operator[](size_t i) const {
return _pool[i];
}
inline Shared::String &operator[](size_t i) {
return _pool[i];
}
inline void Clear() {
_pool.clear();
_count = 0;
}
inline void Reset() {
_count = 0;
}
inline void Add(const char *cstr) {
if (_pool.size() == _count) _pool.resize(_count + 1);
_pool[_count++].SetString(cstr);
}
// An auxiliary line processing buffer
std::vector<char> LineBuf;
private:
std::vector<Shared::String> _pool;
size_t _count; // actual number of lines in use
};
// Break up the text into lines restricted by the given width;
// returns number of lines, or 0 if text cannot be split well to fit in this width
size_t split_lines(const char *texx, SplitLines &lines, int width, int fontNumber, size_t max_lines = -1);
} // namespace AGS3
#endif

View File

@@ -0,0 +1,187 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/font/ttf_font_renderer.h"
#include "ags/lib/alfont/alfont.h"
#include "ags/shared/core/platform.h"
#include "ags/shared/ac/game_version.h"
#include "ags/globals.h"
#include "ags/shared/core/asset_manager.h"
#include "ags/shared/util/stream.h"
#include "ags/shared/ac/game_struct_defines.h"
#include "ags/shared/font/fonts.h"
namespace AGS3 {
using namespace AGS::Shared;
// ***** TTF RENDERER *****
void TTFFontRenderer::AdjustYCoordinateForFont(int *ycoord, int /*fontNumber*/) {
// TTF fonts already have space at the top, so try to remove the gap
// TODO: adding -1 was here before (check the comment above),
// but how universal is this "space at the top"?
// Also, why is this function used only in one case of text rendering?
// Review this after we upgrade the font library.
ycoord[0]--;
}
void TTFFontRenderer::EnsureTextValidForFont(char * /*text*/, int /*fontNumber*/) {
// do nothing, TTF can handle all characters
}
int TTFFontRenderer::GetTextWidth(const char *text, int fontNumber) {
return alfont_text_length(_fontData[fontNumber].AlFont, text);
}
int TTFFontRenderer::GetTextHeight(const char * /*text*/, int fontNumber) {
return alfont_get_font_real_height(_fontData[fontNumber].AlFont);
}
void TTFFontRenderer::RenderText(const char *text, int fontNumber, BITMAP *destination, int x, int y, int colour) {
if (y > destination->cb) // optimisation
return;
// Y - 1 because it seems to get drawn down a bit
if ((ShouldAntiAliasText()) && (bitmap_color_depth(destination) > 8))
alfont_textout_aa(destination, _fontData[fontNumber].AlFont, text, x, y - 1, colour);
else
alfont_textout(destination, _fontData[fontNumber].AlFont, text, x, y - 1, colour);
}
bool TTFFontRenderer::LoadFromDisk(int fontNumber, int fontSize) {
return LoadFromDiskEx(fontNumber, fontSize, nullptr, nullptr, nullptr);
}
bool TTFFontRenderer::IsBitmapFont() {
return false;
}
static int GetAlfontFlags(int load_mode) {
int flags = ALFONT_FLG_FORCE_RESIZE | ALFONT_FLG_SELECT_NOMINAL_SZ;
// Compatibility: font ascender is always adjusted to the formal font's height;
// EXCEPTION: not if it's a game made before AGS 3.4.1 with TTF anti-aliasing
// (the reason is uncertain, but this is to emulate old engine's behavior).
if (((load_mode & FFLG_ASCENDERFIXUP) != 0) &&
!(ShouldAntiAliasText() && (_G(loaded_game_file_version) < kGameVersion_341)))
flags |= ALFONT_FLG_ASCENDER_EQ_HEIGHT;
// Precalculate real glyphs extent (will make loading fonts relatively slower)
flags |= ALFONT_FLG_PRECALC_MAX_CBOX;
return flags;
}
// Loads a TTF font of a certain size
static ALFONT_FONT *LoadTTF(const String &filename, int fontSize, int alfont_flags) {
std::unique_ptr<Stream> reader(_GP(AssetMgr)->OpenAsset(filename));
if (!reader)
return nullptr;
const size_t lenof = reader->GetLength();
std::vector<char> buf; buf.resize(lenof);
reader->Read(&buf.front(), lenof);
reader.reset();
ALFONT_FONT *alfptr = alfont_load_font_from_mem(&buf.front(), lenof);
if (!alfptr)
return nullptr;
alfont_set_font_size_ex(alfptr, fontSize, alfont_flags);
return alfptr;
}
// Fill the FontMetrics struct from the given ALFONT
static void FillMetrics(ALFONT_FONT *alfptr, FontMetrics *metrics) {
metrics->NominalHeight = alfont_get_font_height(alfptr);
metrics->RealHeight = alfont_get_font_real_height(alfptr);
metrics->CompatHeight = metrics->NominalHeight; // just set to default here
alfont_get_font_real_vextent(alfptr, &metrics->VExtent.first, &metrics->VExtent.second);
// fixup vextent to be *not less* than realheight
metrics->VExtent.first = std::min(0, metrics->VExtent.first);
metrics->VExtent.second = std::max(metrics->RealHeight, metrics->VExtent.second);
}
bool TTFFontRenderer::LoadFromDiskEx(int fontNumber, int fontSize, String *src_filename,
const FontRenderParams *params, FontMetrics *metrics) {
String filename = String::FromFormat("agsfnt%d.ttf", fontNumber);
if (fontSize <= 0)
fontSize = 8; // compatibility fix
assert(params);
FontRenderParams f_params = params ? *params : FontRenderParams();
if (f_params.SizeMultiplier > 1)
fontSize *= f_params.SizeMultiplier;
ALFONT_FONT *alfptr = LoadTTF(filename, fontSize,
GetAlfontFlags(f_params.LoadMode));
if (!alfptr)
return false;
_fontData[fontNumber].AlFont = alfptr;
_fontData[fontNumber].Params = f_params;
if (src_filename)
*src_filename = filename;
if (metrics)
FillMetrics(alfptr, metrics);
return true;
}
const char *TTFFontRenderer::GetFontName(int fontNumber) {
return alfont_get_name(_fontData[fontNumber].AlFont);
}
int TTFFontRenderer::GetFontHeight(int fontNumber) {
return alfont_get_font_real_height(_fontData[fontNumber].AlFont);
}
void TTFFontRenderer::GetFontMetrics(int fontNumber, FontMetrics *metrics) {
FillMetrics(_fontData[fontNumber].AlFont, metrics);
}
void TTFFontRenderer::AdjustFontForAntiAlias(int fontNumber, bool /*aa_mode*/) {
if (_G(loaded_game_file_version) < kGameVersion_341) {
ALFONT_FONT *alfptr = _fontData[fontNumber].AlFont;
const FontRenderParams &params = _fontData[fontNumber].Params;
int old_height = alfont_get_font_height(alfptr);
alfont_set_font_size_ex(alfptr, old_height, GetAlfontFlags(params.LoadMode));
}
}
void TTFFontRenderer::FreeMemory(int fontNumber) {
alfont_destroy_font(_fontData[fontNumber].AlFont);
_fontData.erase(fontNumber);
}
bool TTFFontRenderer::MeasureFontOfPointSize(const String &filename, int size_pt, FontMetrics *metrics) {
ALFONT_FONT *alfptr = LoadTTF(filename, size_pt, ALFONT_FLG_FORCE_RESIZE | ALFONT_FLG_SELECT_NOMINAL_SZ);
if (!alfptr)
return false;
FillMetrics(alfptr, metrics);
alfont_destroy_font(alfptr);
return true;
}
bool TTFFontRenderer::MeasureFontOfPixelHeight(const String &filename, int pixel_height, FontMetrics *metrics) {
ALFONT_FONT *alfptr = LoadTTF(filename, pixel_height, ALFONT_FLG_FORCE_RESIZE);
if (!alfptr)
return false;
FillMetrics(alfptr, metrics);
alfont_destroy_font(alfptr);
return true;
}
} // namespace AGS3

View File

@@ -0,0 +1,82 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_FONT_TTF_FONT_RENDERER_H
#define AGS_SHARED_FONT_TTF_FONT_RENDERER_H
#include "common/std/map.h"
#include "ags/shared/font/ags_font_renderer.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
struct ALFONT_FONT;
class TTFFontRenderer : public IAGSFontRendererInternal {
public:
virtual ~TTFFontRenderer() {}
// IAGSFontRenderer implementation
bool LoadFromDisk(int fontNumber, int fontSize) override;
void FreeMemory(int fontNumber) override;
bool SupportsExtendedCharacters(int /*fontNumber*/) override {
return true;
}
int GetTextWidth(const char *text, int fontNumber) override;
int GetTextHeight(const char *text, int fontNumber) override;
void RenderText(const char *text, int fontNumber, BITMAP *destination, int x, int y, int colour) override;
void AdjustYCoordinateForFont(int *ycoord, int fontNumber) override;
void EnsureTextValidForFont(char *text, int fontNumber) override;
// IAGSFontRenderer2 implementation
int GetVersion() override { return 26; /* first compatible engine API version */ }
const char *GetRendererName() override { return "TTFFontRenderer"; }
const char *GetFontName(int fontNumber) override;
int GetFontHeight(int fontNumber) override;
int GetLineSpacing(int fontNumber) override { return 0; /* no specific spacing */ }
// IAGSFontRendererInternal implementation
bool IsBitmapFont() override;
bool LoadFromDiskEx(int fontNumber, int fontSize, AGS::Shared::String *src_filename,
const FontRenderParams *params, FontMetrics *metrics) override;
void GetFontMetrics(int fontNumber, FontMetrics *metrics) override;
void AdjustFontForAntiAlias(int fontNumber, bool aa_mode) override;
//
// Utility functions
//
// Try load the TTF font using provided point size, and report its metrics
static bool MeasureFontOfPointSize(const AGS::Shared::String &filename, int size_pt, FontMetrics *metrics);
// Try load the TTF font, find the point size which results in pixel height
// as close to the requested as possible; report its metrics
static bool MeasureFontOfPixelHeight(const AGS::Shared::String &filename, int pixel_height, FontMetrics *metrics);
private:
struct FontData {
ALFONT_FONT *AlFont;
FontRenderParams Params;
};
std::map<int, FontData> _fontData;
};
} // namespace AGS3
#endif

View File

@@ -0,0 +1,196 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/std/algorithm.h"
#include "ags/shared/font/wfn_font.h"
#include "ags/shared/debugging/out.h"
#include "ags/shared/util/memory.h"
#include "ags/shared/util/stream.h"
#include "ags/globals.h"
namespace AGS3 {
using namespace AGS::Shared;
static const char *WFN_FILE_SIGNATURE = "WGT Font File ";
static const size_t WFN_FILE_SIG_LENGTH = 15;
static const size_t MinCharDataSize = sizeof(uint16_t) * 2;
WFNChar::WFNChar()
: Width(0)
, Height(0)
, Data(nullptr) {
}
void WFNChar::RestrictToBytes(size_t bytes) {
if (bytes < GetRequiredPixelSize())
Height = static_cast<uint16_t>(bytes / GetRowByteCount());
}
const WFNChar &WFNFont::GetChar(uint16_t code) const {
return code < _refs.size() ? *_refs[code] : _G(emptyChar);
}
void WFNFont::Clear() {
_refs.clear();
_items.clear();
_pixelData.clear();
}
WFNError WFNFont::ReadFromFile(Stream *in, const soff_t data_size) {
Clear();
const soff_t used_data_size = data_size > 0 ? data_size : in->GetLength();
// Read font header
char sig[WFN_FILE_SIG_LENGTH];
in->Read(sig, WFN_FILE_SIG_LENGTH);
if (strncmp(sig, WFN_FILE_SIGNATURE, WFN_FILE_SIG_LENGTH) != 0) {
Debug::Printf(kDbgMsg_Error, "\tWFN: bad format signature");
return kWFNErr_BadSignature; // bad format
}
const soff_t table_addr = static_cast<uint16_t>(in->ReadInt16()); // offset table relative address
if (table_addr < (soff_t)(WFN_FILE_SIG_LENGTH + sizeof(uint16_t)) || table_addr >= used_data_size) {
Debug::Printf(kDbgMsg_Error, "\tWFN: bad table address: %llu (%llu - %llu)", static_cast<int64>(table_addr),
static_cast<int64>(WFN_FILE_SIG_LENGTH + sizeof(uint16_t)), static_cast<int64>(used_data_size));
return kWFNErr_BadTableAddress; // bad table address
}
const soff_t offset_table_size = used_data_size - table_addr;
const soff_t raw_data_offset = WFN_FILE_SIG_LENGTH + sizeof(uint16_t);
const size_t total_char_data = static_cast<size_t>(table_addr - raw_data_offset);
const size_t char_count = static_cast<size_t>(offset_table_size / sizeof(uint16_t));
// We process character data in three steps:
// 1. For every character store offset of character item, excluding
// duplicates.
// 2. Allocate memory for character items and pixel array and copy
// appropriate data; test for possible format corruption.
// 3. Create array of references from characters to items; same item may be
// referenced by many characters.
WFNError err = kWFNErr_NoError;
if (total_char_data == 0u || char_count == 0u)
return kWFNErr_NoError; // no items
// Read character data array
std::vector<uint8_t> raw_data; raw_data.resize(total_char_data);
in->Read(&raw_data.front(), total_char_data);
// Read offset table
std::vector<uint16_t> offset_table; offset_table.resize(char_count);
in->ReadArrayOfInt16(reinterpret_cast<int16_t *>(&offset_table.front()), char_count);
// Read all referenced offsets in an unsorted vector
std::vector<uint16_t> offs;
offs.reserve(char_count); // reserve max possible offsets
for (size_t i = 0; i < char_count; ++i) {
const uint16_t off = offset_table[i];
if (off < raw_data_offset || (soff_t)(off + MinCharDataSize) > table_addr) {
Debug::Printf("\tWFN: character %d -- bad item offset: %d (%d - %d, +%d)",
i, off, raw_data_offset, table_addr, MinCharDataSize);
err = kWFNErr_HasBadCharacters; // warn about potentially corrupt format
continue; // bad character offset
}
offs.push_back(off);
}
// sort offsets vector and remove any duplicates
std::sort(offs.begin(), offs.end());
#if AGS_PLATFORM_SCUMMVM
// TODO: See if this works correctly
std::unique(offs.begin(), offs.end());
#else
std::vector<uint16_t>(offs.begin(), std::unique(offs.begin(), offs.end())).swap(offs);
#endif
// Now that we know number of valid character items, parse and store character data
WFNChar init_ch;
_items.resize(offs.size());
size_t total_pixel_size = 0;
for (size_t i = 0; i < _items.size(); ++i) {
const uint8_t *p_data = &raw_data[offs[i] - raw_data_offset];
init_ch.Width = Memory::ReadInt16LE(p_data);
init_ch.Height = Memory::ReadInt16LE(p_data + sizeof(uint16_t));
total_pixel_size += init_ch.GetRequiredPixelSize();
_items[i] = init_ch;
}
// Now that we know actual size of pixels in use, create pixel data array;
// since the items are sorted, the pixel data will be stored sequentially as well.
// At this point offs and _items have related elements in the same order.
_pixelData.resize(total_pixel_size);
std::vector<uint8_t>::iterator pixel_it = _pixelData.begin(); // write ptr
for (size_t i = 0; i < _items.size(); ++i) {
const size_t pixel_data_size = _items[i].GetRequiredPixelSize();
if (pixel_data_size == 0) {
Debug::Printf("\tWFN: item at off %d -- null size", offs[i]);
err = kWFNErr_HasBadCharacters;
continue; // just an empty character
}
const uint16_t raw_off = offs[i] - raw_data_offset + MinCharDataSize; // offset in raw array
size_t src_size = pixel_data_size;
if (i + 1 != _items.size() && (soff_t)(raw_off + src_size) > offs[i + 1] - raw_data_offset) { // character pixel data overlaps next character
Debug::Printf("\tWFN: item at off %d -- pixel data overlaps next known item (at %d, +%d)",
offs[i], offs[i + 1], MinCharDataSize + src_size);
err = kWFNErr_HasBadCharacters; // warn about potentially corrupt format
src_size = offs[i + 1] - offs[i] - MinCharDataSize;
}
if (raw_off + src_size > total_char_data) { // character pixel data overflow buffer
Debug::Printf("\tWFN: item at off %d -- pixel data exceeds available data (at %d, +%d)",
offs[i], table_addr, MinCharDataSize + src_size);
err = kWFNErr_HasBadCharacters; // warn about potentially corrupt format
src_size = total_char_data - raw_off;
}
_items[i].RestrictToBytes(src_size);
assert(pixel_it + pixel_data_size <= _pixelData.end()); // should not normally fail
Common::copy(raw_data.begin() + raw_off, raw_data.begin() + (raw_off + src_size), pixel_it);
_items[i].Data = &(*pixel_it);
pixel_it += pixel_data_size;
}
// Create final reference array
_refs.resize(char_count);
for (size_t i = 0; i < char_count; ++i) {
const uint16_t off = offset_table[i];
// if bad character offset - reference empty character
if (off < raw_data_offset || (soff_t)(off + MinCharDataSize) > table_addr) {
_refs[i] = &_G(emptyChar);
} else {
// in usual case the offset table references items in strict order
if (i < _items.size() && offs[i] == off)
_refs[i] = &_items[i];
else {
// we know beforehand that such item must exist
std::vector<uint16_t>::const_iterator at = std::lower_bound(offs.begin(), offs.end(), off);
assert(at != offs.end() && *at == off && // should not normally fail
at - offs.begin() >= 0 && static_cast<size_t>(at - offs.begin()) < _items.size());
_refs[i] = &_items[at - offs.begin()]; // set up reference to item
}
}
}
return err;
}
} // namespace AGS3

View File

@@ -0,0 +1,108 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// WFNFont - an immutable AGS font object.
//
//-----------------------------------------------------------------------------
//
// WFN format:
// - signature ( 15 )
// - offsets table offset ( 2 )
// - characters table (for unknown number of char items):
// - width ( 2 )
// - height ( 2 )
// - pixel bits ( (width / 8 + 1) * height )
// - any unknown data
// - offsets table (for X chars):
// - character offset ( 2 )
//
// NOTE: unfortunately, at the moment the format does not provide means to
// know the number of supported characters for certain, and the size of the
// data (file) is used to determine that.
//
//=============================================================================
#ifndef AGS_SHARED_FONT_WFN_FONT_H
#define AGS_SHARED_FONT_WFN_FONT_H
#include "common/std/vector.h"
#include "ags/shared/core/types.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class Stream;
} // namespace Shared
} // namespace AGS
enum WFNError {
kWFNErr_NoError,
kWFNErr_BadSignature,
kWFNErr_BadTableAddress,
kWFNErr_HasBadCharacters
};
struct WFNChar {
uint16_t Width;
uint16_t Height;
const uint8_t *Data;
WFNChar();
inline size_t GetRowByteCount() const {
return (Width + 7) / 8;
}
inline size_t GetRequiredPixelSize() const {
return GetRowByteCount() * Height;
}
// Ensure character's width & height fit in given number of pixel bytes
void RestrictToBytes(size_t bytes);
};
class WFNFont {
public:
inline uint16_t GetCharCount() const {
return static_cast<uint16_t>(_refs.size());
}
// Get WFN character for the given code; if the character is missing, returns empty character
const WFNChar &GetChar(uint16_t code) const;
void Clear();
// Reads WFNFont object, using data_size bytes from stream; if data_size = 0,
// the available stream's length is used instead. Returns error code.
WFNError ReadFromFile(AGS::Shared::Stream *in, const soff_t data_size = 0);
protected:
std::vector<const WFNChar *> _refs; // reference array, contains pointers to elements of _items
std::vector<WFNChar> _items; // actual character items
std::vector<uint8_t> _pixelData; // pixel data array
};
} // namespace AGS3
#endif

View File

@@ -0,0 +1,163 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/font/wfn_font_renderer.h"
#include "ags/shared/ac/common.h" // our_eip
#include "ags/shared/core/asset_manager.h"
#include "ags/shared/debugging/out.h"
#include "ags/shared/font/wfn_font.h"
#include "ags/shared/gfx/bitmap.h"
#include "ags/shared/util/stream.h"
#include "ags/globals.h"
namespace AGS3 {
using namespace AGS::Shared;
void WFNFontRenderer::AdjustYCoordinateForFont(int *ycoord, int fontNumber) {
// Do nothing
}
void WFNFontRenderer::EnsureTextValidForFont(char *text, int fontNumber) {
// Do nothing
}
int WFNFontRenderer::GetTextWidth(const char *text, int fontNumber) {
const WFNFont *font = _fontData[fontNumber].Font;
const FontRenderParams &params = _fontData[fontNumber].Params;
int text_width = 0;
for (int code = ugetxc(&text); code; code = ugetxc(&text)) {
text_width += font->GetChar(code).Width;
}
return text_width * params.SizeMultiplier;
}
int WFNFontRenderer::GetTextHeight(const char *text, int fontNumber) {
const WFNFont *font = _fontData[fontNumber].Font;
const FontRenderParams &params = _fontData[fontNumber].Params;
int max_height = 0;
for (int code = ugetxc(&text); code; code = ugetxc(&text)) {
const uint16_t height = font->GetChar(code).Height;
max_height = std::max(max_height, static_cast<int>(height));
}
return max_height * params.SizeMultiplier;
}
static int RenderChar(Bitmap *ds, const int at_x, const int at_y, Rect clip,
const WFNChar &wfn_char, const int scale, const color_t text_color);
void WFNFontRenderer::RenderText(const char *text, int fontNumber, BITMAP *destination, int x, int y, int colour) {
int oldeip = get_our_eip();
set_our_eip(415);
const WFNFont *font = _fontData[fontNumber].Font;
const FontRenderParams &params = _fontData[fontNumber].Params;
Bitmap ds(destination, true);
// NOTE: allegro's putpixel ignores clipping (optimization),
// so we'll have to accommodate for that ourselves
Rect clip = ds.GetClip();
for (int code = ugetxc(&text); code; code = ugetxc(&text))
x += RenderChar(&ds, x, y, clip, font->GetChar(code), params.SizeMultiplier, colour);
set_our_eip(oldeip);
}
static int RenderChar(Bitmap *ds, const int at_x, const int at_y, Rect clip,
const WFNChar &wfn_char, const int scale, const color_t text_color) {
const int width = wfn_char.Width;
const int height = wfn_char.Height;
const unsigned char *actdata = wfn_char.Data;
const int bytewid = wfn_char.GetRowByteCount();
int sx = std::max(at_x, clip.Left), ex = clip.Right + 1;
int sy = std::max(at_y, clip.Top), ey = clip.Bottom + 1;
int sw = std::max(0, clip.Left - at_x);
int sh = std::max(0, clip.Top - at_y);
for (int h = sh, y = sy; h < height && y < ey; ++h, y += scale) {
for (int w = sw, x = sx; w < width && x < ex; ++w, x += scale) {
if (((actdata[h * bytewid + (w / 8)] & (0x80 >> (w % 8))) != 0)) {
if (scale > 1) {
ds->FillRect(RectWH(x, y, scale, scale), text_color);
} else {
ds->PutPixel(x, y, text_color);
}
}
}
}
return width * scale;
}
bool WFNFontRenderer::LoadFromDisk(int fontNumber, int fontSize) {
return LoadFromDiskEx(fontNumber, fontSize, nullptr, nullptr, nullptr);
}
bool WFNFontRenderer::IsBitmapFont() {
return true;
}
bool WFNFontRenderer::LoadFromDiskEx(int fontNumber, int /*fontSize*/, String *src_filename,
const FontRenderParams *params, FontMetrics *metrics) {
String file_name;
Stream *ffi = nullptr;
file_name.Format("agsfnt%d.wfn", fontNumber);
ffi = _GP(AssetMgr)->OpenAsset(file_name);
if (ffi == nullptr) {
// actual font not found, try font 0 instead
// FIXME: this should not be done here in this font renderer implementation,
// but somewhere outside, when whoever calls this method
file_name = "agsfnt0.wfn";
ffi = _GP(AssetMgr)->OpenAsset(file_name);
if (ffi == nullptr)
return false;
}
WFNFont *font = new WFNFont();
WFNError err = font->ReadFromFile(ffi);
delete ffi;
if (err == kWFNErr_HasBadCharacters)
Debug::Printf(kDbgMsg_Warn, "WARNING: font '%s' has mistakes in data format, some characters may be displayed incorrectly", file_name.GetCStr());
else if (err != kWFNErr_NoError) {
delete font;
return false;
}
_fontData[fontNumber].Font = font;
_fontData[fontNumber].Params = params ? *params : FontRenderParams();
if (src_filename)
*src_filename = file_name;
if (metrics)
*metrics = FontMetrics();
return true;
}
void WFNFontRenderer::FreeMemory(int fontNumber) {
delete _fontData[fontNumber].Font;
_fontData.erase(fontNumber);
}
bool WFNFontRenderer::SupportsExtendedCharacters(int fontNumber) {
return _fontData[fontNumber].Font->GetCharCount() > 128;
}
} // namespace AGS3

View File

@@ -0,0 +1,71 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_FONT_WFN_FONT_RENDERER_H
#define AGS_SHARED_FONT_WFN_FONT_RENDERER_H
#include "common/std/map.h"
#include "ags/lib/std.h"
#include "ags/shared/font/ags_font_renderer.h"
namespace AGS3 {
class WFNFont;
class WFNFontRenderer : public IAGSFontRendererInternal {
public:
// IAGSFontRenderer implementation
virtual ~WFNFontRenderer() {}
bool LoadFromDisk(int fontNumber, int fontSize) override;
void FreeMemory(int fontNumber) override;
bool SupportsExtendedCharacters(int fontNumber) override;
int GetTextWidth(const char *text, int fontNumber) override;
int GetTextHeight(const char *text, int fontNumber) override;
void RenderText(const char *text, int fontNumber, BITMAP *destination, int x, int y, int colour) override;
void AdjustYCoordinateForFont(int *ycoord, int fontNumber) override;
void EnsureTextValidForFont(char *text, int fontNumber) override;
// IAGSFontRenderer2 implementation
int GetVersion() override { return 26; /* first compatible engine API version */ }
const char *GetRendererName() override { return "WFNFontRenderer"; }
const char *GetFontName(int /*fontNumber*/) override { return ""; }
int GetFontHeight(int fontNumber) override { return 0; /* TODO? */ }
int GetLineSpacing(int fontNumber) override { return 0; /* no specific spacing */ }
// IAGSFontRendererInternal implementation
bool IsBitmapFont() override;
bool LoadFromDiskEx(int fontNumber, int fontSize, AGS::Shared::String *src_filename,
const FontRenderParams *params, FontMetrics *metrics) override;
void GetFontMetrics(int fontNumber, FontMetrics *metrics) override { *metrics = FontMetrics(); }
void AdjustFontForAntiAlias(int /*fontNumber*/, bool /*aa_mode*/) override { /* do nothing */ }
private:
struct FontData {
WFNFont *Font;
FontRenderParams Params;
};
std::map<int, FontData> _fontData;
};
} // namespace AGS3
#endif

View File

@@ -0,0 +1,129 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/game/custom_properties.h"
#include "ags/shared/util/stream.h"
#include "ags/shared/util/string_utils.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
PropertyDesc::PropertyDesc() {
Type = kPropertyBoolean;
}
PropertyDesc::PropertyDesc(const String &name, PropertyType type, const String &desc, const String &def_value) {
Name = name;
Type = type;
Description = desc;
DefaultValue = def_value;
}
namespace Properties {
PropertyError ReadSchema(PropertySchema &schema, Stream *in) {
PropertyVersion version = (PropertyVersion)in->ReadInt32();
if (version < kPropertyVersion_Initial ||
version > kPropertyVersion_Current) {
return kPropertyErr_UnsupportedFormat;
}
PropertyDesc prop;
int count = in->ReadInt32();
if (version == kPropertyVersion_Initial) {
for (int i = 0; i < count; ++i) {
prop.Name.Read(in, LEGACY_MAX_CUSTOM_PROP_SCHEMA_NAME_LENGTH);
prop.Description.Read(in, LEGACY_MAX_CUSTOM_PROP_DESC_LENGTH);
prop.DefaultValue.Read(in, LEGACY_MAX_CUSTOM_PROP_VALUE_LENGTH);
prop.Type = (PropertyType)in->ReadInt32();
schema[prop.Name] = prop;
}
} else {
for (int i = 0; i < count; ++i) {
prop.Name = StrUtil::ReadString(in);
prop.Type = (PropertyType)in->ReadInt32();
prop.Description = StrUtil::ReadString(in);
prop.DefaultValue = StrUtil::ReadString(in);
schema[prop.Name] = prop;
}
}
return kPropertyErr_NoError;
}
void WriteSchema(const PropertySchema &schema, Stream *out) {
out->WriteInt32(kPropertyVersion_Current);
out->WriteInt32(schema.size());
for (PropertySchema::const_iterator it = schema.begin();
it != schema.end(); ++it) {
const PropertyDesc &prop = it->_value;
StrUtil::WriteString(prop.Name, out);
out->WriteInt32(prop.Type);
StrUtil::WriteString(prop.Description, out);
StrUtil::WriteString(prop.DefaultValue, out);
}
}
PropertyError ReadValues(StringIMap &map, Stream *in, bool aligned) {
PropertyVersion version = (PropertyVersion)in->ReadInt32();
if (version < kPropertyVersion_Initial ||
version > kPropertyVersion_Current) {
return kPropertyErr_UnsupportedFormat;
}
int count = in->ReadInt32();
if (version == kPropertyVersion_Initial) {
for (int i = 0; i < count; ++i) {
String name = String::FromStream(in, LEGACY_MAX_CUSTOM_PROP_NAME_LENGTH);
map[name] = String::FromStream(in, LEGACY_MAX_CUSTOM_PROP_VALUE_LENGTH);
}
} else {
if (aligned) {
for (int i = 0; i < count; ++i) {
String name = StrUtil::ReadStringAligned(in);
map[name] = StrUtil::ReadStringAligned(in);
}
} else {
for (int i = 0; i < count; ++i) {
String name = StrUtil::ReadString(in);
map[name] = StrUtil::ReadString(in);
}
}
}
return kPropertyErr_NoError;
}
void WriteValues(const StringIMap &map, Stream *out) {
out->WriteInt32(kPropertyVersion_Current);
out->WriteInt32(map.size());
for (StringIMap::const_iterator it = map.begin();
it != map.end(); ++it) {
StrUtil::WriteString(it->_key, out);
StrUtil::WriteString(it->_value, out);
}
}
} // namespace Properties
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,109 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// Custom property structs
//
//-----------------------------------------------------------------------------
//
// Custom property schema is kept by GameSetupStruct object as a single
// instance and defines property type and default value. Every game entity that
// has properties implemented keeps CustomProperties object, which stores
// actual property values only if ones are different from defaults.
//
//=============================================================================
#ifndef AGS_SHARED_GAME_CUSTOM_PROPERTIES_H
#define AGS_SHARED_GAME_CUSTOM_PROPERTIES_H
#include "common/std/map.h"
#include "ags/shared/util/string.h"
#include "ags/shared/util/string_types.h"
namespace AGS3 {
#define LEGACY_MAX_CUSTOM_PROPERTIES 30
// NOTE: for some reason the property name stored in schema object was limited
// to only 20 characters, while the custom properties map could hold up to 200.
// Whether this was an error or design choice is unknown.
#define LEGACY_MAX_CUSTOM_PROP_SCHEMA_NAME_LENGTH 20
#define LEGACY_MAX_CUSTOM_PROP_NAME_LENGTH 200
#define LEGACY_MAX_CUSTOM_PROP_DESC_LENGTH 100
#define LEGACY_MAX_CUSTOM_PROP_VALUE_LENGTH 500
namespace AGS {
namespace Shared {
enum PropertyVersion {
kPropertyVersion_Initial = 1,
kPropertyVersion_340,
kPropertyVersion_Current = kPropertyVersion_340
};
enum PropertyType {
kPropertyUndefined = 0,
kPropertyBoolean,
kPropertyInteger,
kPropertyString
};
enum PropertyError {
kPropertyErr_NoError,
kPropertyErr_UnsupportedFormat
};
//
// PropertyDesc - a description of a single custom property
//
struct PropertyDesc {
String Name;
PropertyType Type;
String Description;
String DefaultValue;
PropertyDesc();
PropertyDesc(const String &name, PropertyType type, const String &desc, const String &def_value);
};
// NOTE: AGS has case-insensitive property IDs
// Schema - a map of property descriptions
typedef std::unordered_map<String, PropertyDesc, IgnoreCase_Hash, IgnoreCase_EqualTo> PropertySchema;
namespace Properties {
PropertyError ReadSchema(PropertySchema &schema, Stream *in);
void WriteSchema(const PropertySchema &schema, Stream *out);
// Reads property values from the stream and assign them to map.
// The non-matching existing map items, if any, are NOT erased.
// NOTE: "aligned" parameter is for legacy saves support only.
PropertyError ReadValues(StringIMap &map, Stream *in, bool aligned = false);
// Writes property values chunk to the stream
void WriteValues(const StringIMap &map, Stream *out);
} // namespace Properties
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,375 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//include <string.h>
#include "ags/shared/ac/common.h" // quit
#include "ags/shared/game/interactions.h"
#include "ags/shared/util/stream.h"
#include "ags/shared/util/math.h"
#include "common/util.h"
namespace AGS3 {
using namespace AGS::Shared;
namespace AGS {
namespace Shared {
InteractionValue::InteractionValue() {
Type = kInterValLiteralInt;
Value = 0;
Extra = 0;
}
void InteractionValue::clear() {
Type = kInterValInvalid;
Value = 0;
Extra = 0;
}
void InteractionValue::Read(Stream *in) {
Type = (InterValType)in->ReadInt8();
in->Seek(3); // alignment padding to int32
Value = in->ReadInt32();
Extra = in->ReadInt32();
}
void InteractionValue::Write(Stream *out) const {
out->WriteInt8(Type);
out->WriteByteCount(0, 3); // alignment padding to int32
out->WriteInt32(Value);
out->WriteInt32(Extra);
}
//-----------------------------------------------------------------------------
InteractionCommand::InteractionCommand()
: Type(0)
, Parent(nullptr) {
}
InteractionCommand::InteractionCommand(const InteractionCommand &ic) {
*this = ic;
}
void InteractionCommand::Assign(const InteractionCommand &ic, InteractionCommandList *parent) {
Type = ic.Type;
memcpy(Data, ic.Data, sizeof(Data));
Children.reset(ic.Children.get() ? new InteractionCommandList(*ic.Children) : nullptr);
Parent = parent;
}
void InteractionCommand::Reset() {
Type = 0;
for (uint8 i = 0; i < ARRAYSIZE(Data); i++) Data[i].clear();
Children.reset();
Parent = nullptr;
}
void InteractionCommand::ReadValues(Stream *in) {
for (int i = 0; i < MAX_ACTION_ARGS; ++i) {
Data[i].Read(in);
}
}
void InteractionCommand::Read(Stream *in, bool &has_children) {
in->ReadInt32(); // skip the 32-bit vtbl ptr (the old serialization peculiarity)
Type = in->ReadInt32();
ReadValues(in);
has_children = in->ReadInt32() != 0;
in->ReadInt32(); // skip 32-bit Parent pointer
}
void InteractionCommand::WriteValues(Stream *out) const {
for (int i = 0; i < MAX_ACTION_ARGS; ++i) {
Data[i].Write(out);
}
}
void InteractionCommand::Write(Stream *out) const {
out->WriteInt32(0); // write dummy 32-bit vtbl ptr
out->WriteInt32(Type);
WriteValues(out);
out->WriteInt32(Children.get() ? 1 : 0); // notify that has children
out->WriteInt32(0); // skip 32-bit Parent pointer
}
InteractionCommand &InteractionCommand::operator=(const InteractionCommand &ic) {
if (this == &ic) {
return *this; // prevent self-assignment
}
Type = ic.Type;
memcpy(Data, ic.Data, sizeof(Data));
Children.reset(ic.Children.get() ? new InteractionCommandList(*ic.Children) : nullptr);
Parent = ic.Parent;
return *this;
}
//-----------------------------------------------------------------------------
InteractionCommandList::InteractionCommandList()
: TimesRun(0) {
}
InteractionCommandList::InteractionCommandList(const InteractionCommandList &icmd_list) {
TimesRun = icmd_list.TimesRun;
Cmds.resize(icmd_list.Cmds.size());
for (size_t i = 0; i < icmd_list.Cmds.size(); ++i) {
Cmds[i].Assign(icmd_list.Cmds[i], this);
}
}
void InteractionCommandList::Reset() {
Cmds.clear();
TimesRun = 0;
}
void InteractionCommandList::ReadCommands(Stream *in, std::vector<bool> &cmd_children) {
for (size_t i = 0; i < Cmds.size(); ++i) {
bool has_children;
Cmds[i].Read(in, has_children);
cmd_children[i] = has_children;
}
}
void InteractionCommandList::Read(Stream *in) {
size_t cmd_count = in->ReadInt32();
TimesRun = in->ReadInt32();
std::vector<bool> cmd_children;
Cmds.resize(cmd_count);
cmd_children.resize(cmd_count);
ReadCommands(in, cmd_children);
for (size_t i = 0; i < cmd_count; ++i) {
if (cmd_children[i]) {
Cmds[i].Children.reset(new InteractionCommandList());
Cmds[i].Children->Read(in);
}
Cmds[i].Parent = this;
}
}
void InteractionCommandList::WriteCommands(Stream *out) const {
for (InterCmdVector::const_iterator it = Cmds.begin(); it != Cmds.end(); ++it) {
it->Write(out);
}
}
void InteractionCommandList::Write(Stream *out) const {
size_t cmd_count = Cmds.size();
out->WriteInt32(cmd_count);
out->WriteInt32(TimesRun);
WriteCommands(out);
for (size_t i = 0; i < cmd_count; ++i) {
if (Cmds[i].Children.get() != nullptr)
Cmds[i].Children->Write(out);
}
}
//-----------------------------------------------------------------------------
InteractionEvent::InteractionEvent()
: Type(0)
, TimesRun(0) {
}
InteractionEvent::InteractionEvent(const InteractionEvent &ie) {
*this = ie;
}
InteractionEvent &InteractionEvent::operator = (const InteractionEvent &ie) {
Type = ie.Type;
TimesRun = ie.TimesRun;
Response.reset(ie.Response.get() ? new InteractionCommandList(*ie.Response) : nullptr);
return *this;
}
//-----------------------------------------------------------------------------
Interaction::Interaction() {
}
Interaction::Interaction(const Interaction &ni) {
*this = ni;
}
Interaction &Interaction::operator =(const Interaction &ni) {
Events.resize(ni.Events.size());
for (size_t i = 0; i < ni.Events.size(); ++i) {
Events[i] = InteractionEvent(ni.Events[i]);
}
return *this;
}
void Interaction::CopyTimesRun(const Interaction &inter) {
assert(Events.size() == inter.Events.size());
size_t count = MIN(Events.size(), inter.Events.size());
for (size_t i = 0; i < count; ++i) {
Events[i].TimesRun = inter.Events[i].TimesRun;
}
}
void Interaction::Reset() {
Events.clear();
}
Interaction *Interaction::CreateFromStream(Stream *in) {
if (in->ReadInt32() != kInteractionVersion_Initial)
return nullptr; // unsupported format
const size_t evt_count = in->ReadInt32();
if (evt_count > MAX_NEWINTERACTION_EVENTS)
quit("Can't deserialize interaction: too many events");
int32_t types[MAX_NEWINTERACTION_EVENTS];
int32_t load_response[MAX_NEWINTERACTION_EVENTS];
in->ReadArrayOfInt32(types, evt_count);
in->ReadArrayOfInt32(load_response, evt_count);
Interaction *inter = new Interaction();
inter->Events.resize(evt_count);
for (size_t i = 0; i < evt_count; ++i) {
InteractionEvent &evt = inter->Events[i];
evt.Type = types[i];
if (load_response[i] != 0) {
evt.Response.reset(new InteractionCommandList());
evt.Response->Read(in);
}
}
return inter;
}
void Interaction::Write(Stream *out) const {
out->WriteInt32(kInteractionVersion_Initial); // Version
const size_t evt_count = Events.size();
out->WriteInt32(evt_count);
for (size_t i = 0; i < evt_count; ++i) {
out->WriteInt32(Events[i].Type);
}
// The pointer is only checked against NULL to determine whether the event exists
for (size_t i = 0; i < evt_count; ++i) {
out->WriteInt32(Events[i].Response.get() ? 1 : 0);
}
for (size_t i = 0; i < evt_count; ++i) {
if (Events[i].Response.get())
Events[i].Response->Write(out);
}
}
void Interaction::ReadFromSavedgame_v321(Stream *in) {
const size_t evt_count = in->ReadInt32();
if (evt_count > MAX_NEWINTERACTION_EVENTS)
quit("Can't deserialize interaction: too many events");
Events.resize(evt_count);
// Read required amount and skip the remaining placeholders
for (size_t i = 0; i < evt_count; ++i) {
Events[i].Type = in->ReadInt32();
}
in->Seek((MAX_NEWINTERACTION_EVENTS - evt_count) * sizeof(int32_t));
ReadTimesRunFromSave_v321(in);
// Skip an array of dummy 32-bit pointers (nasty legacy format)
in->Seek(MAX_NEWINTERACTION_EVENTS * sizeof(int32_t));
}
void Interaction::WriteToSavedgame_v321(Stream *out) const {
const size_t evt_count = Events.size();
out->WriteInt32(evt_count);
for (size_t i = 0; i < evt_count; ++i) {
out->WriteInt32(Events[i].Type);
}
out->WriteByteCount(0, (MAX_NEWINTERACTION_EVENTS - evt_count) * sizeof(int32_t));
WriteTimesRunToSave_v321(out);
// Array of dummy 32-bit pointers (nasty legacy format)
out->WriteByteCount(0, MAX_NEWINTERACTION_EVENTS * sizeof(int32_t));
}
void Interaction::ReadTimesRunFromSave_v321(Stream *in) {
const size_t evt_count = Events.size();
// Read required amount and skip the remaining placeholders
for (size_t i = 0; i < evt_count; ++i) {
Events[i].TimesRun = in->ReadInt32();
}
in->Seek((MAX_NEWINTERACTION_EVENTS - evt_count) * sizeof(int32_t));
}
void Interaction::WriteTimesRunToSave_v321(Stream *out) const {
const size_t evt_count = Events.size();
for (size_t i = 0; i < Events.size(); ++i) {
out->WriteInt32(Events[i].TimesRun);
}
out->WriteByteCount(0, (MAX_NEWINTERACTION_EVENTS - evt_count) * sizeof(int32_t));
}
//-----------------------------------------------------------------------------
#define INTER_VAR_NAME_LENGTH 23
InteractionVariable::InteractionVariable()
: Type(0)
, Value(0) {
}
InteractionVariable::InteractionVariable(const String &name, char type, int val)
: Name(name)
, Type(type)
, Value(val) {
}
void InteractionVariable::Read(Stream *in) {
Name.ReadCount(in, INTER_VAR_NAME_LENGTH);
Type = in->ReadInt8();
Value = in->ReadInt32();
}
void InteractionVariable::Write(Shared::Stream *out) const {
out->Write(Name.GetCStr(), INTER_VAR_NAME_LENGTH);
out->WriteInt8(Type);
out->WriteInt32(Value);
}
//-----------------------------------------------------------------------------
InteractionScripts *InteractionScripts::CreateFromStream(Stream *in) {
const size_t evt_count = in->ReadInt32();
if (evt_count > MAX_NEWINTERACTION_EVENTS) {
quit("Can't deserialize interaction scripts: too many events");
return nullptr;
}
InteractionScripts *scripts = new InteractionScripts();
for (size_t i = 0; i < evt_count; ++i) {
String name = String::FromStream(in);
scripts->ScriptFuncNames.push_back(name);
}
return scripts;
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,213 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// Interaction structs.
//
//-----------------------------------------------------------------------------
//
// Most of the interaction types here were used before the script and have
// very limited capabilities. They were removed from AGS completely in
// generation 3.0. The code is left for backwards compatibility.
//
//-----------------------------------------------------------------------------
//
/* THE WAY THIS WORKS:
*
* Interaction (Hotspot 1)
* |
* +-- eventTypes [NUM_EVENTS]
* +-- InteractionCommandList [NUM_EVENTS] (Look at hotspot)
* |
* +-- InteractionCommand [NUM_COMMANDS] (Display message)
* |
* +-- InteractionValue [NUM_ARGUMENTS] (5)
*/
//
//=============================================================================
#ifndef AGS_SHARED_GAME_INTEREACTIONS_H
#define AGS_SHARED_GAME_INTEREACTIONS_H
#include "common/std/memory.h"
#include "ags/shared/util/string_types.h"
namespace AGS3 {
#define LOCAL_VARIABLE_OFFSET 10000
#define MAX_GLOBAL_VARIABLES 100
#define MAX_ACTION_ARGS 5
#define MAX_NEWINTERACTION_EVENTS 30
#define MAX_COMMANDS_PER_LIST 40
namespace AGS {
namespace Shared {
enum InterValType : int8_t {
kInterValInvalid = 0,
kInterValLiteralInt = 1,
kInterValVariable = 2,
kInterValBoolean = 3,
kInterValCharnum = 4
};
enum InteractionVersion {
kInteractionVersion_Initial = 1
};
// InteractionValue represents an argument of interaction command
struct InteractionValue {
InterValType Type; // value type
int Value; // value definition
int Extra;
InteractionValue();
void clear();
void Read(Stream *in);
void Write(Stream *out) const;
};
struct InteractionCommandList;
typedef std::unique_ptr<InteractionCommandList> UInterCmdList;
// InteractionCommand represents a single command (action), an item of Command List
struct InteractionCommand {
int Type; // type of action
InteractionValue Data[MAX_ACTION_ARGS]; // action arguments
UInterCmdList Children; // list of sub-actions
InteractionCommandList *Parent; // action parent (optional)
InteractionCommand();
InteractionCommand(const InteractionCommand &ic);
void Assign(const InteractionCommand &ic, InteractionCommandList *parent);
void Reset();
void Read(Stream *in, bool &has_children);
void Write(Stream *out) const;
InteractionCommand &operator = (const InteractionCommand &ic);
private:
void ReadValues(Stream *in);
void WriteValues(Stream *out) const;
};
typedef std::vector<InteractionCommand> InterCmdVector;
// InteractionCommandList represents a list of commands (actions) that need to be
// performed on particular game event
struct InteractionCommandList {
InterCmdVector Cmds; // actions to run
int TimesRun; // used by engine to track score changes
InteractionCommandList();
InteractionCommandList(const InteractionCommandList &icmd_list);
void Reset();
void Read(Stream *in);
void Write(Stream *out) const;
protected:
void ReadCommands(Shared::Stream *in, std::vector<bool> &cmd_children);
void WriteCommands(Shared::Stream *out) const;
};
// InteractionEvent is a single event with a list of commands to performed
struct InteractionEvent {
int Type; // type of event
int TimesRun; // used by engine to track score changes
UInterCmdList Response; // list of commands to run
InteractionEvent();
InteractionEvent(const InteractionEvent &ie);
InteractionEvent &operator = (const InteractionEvent &ic);
};
typedef std::vector<InteractionEvent> InterEvtVector;
// Interaction is the list of events and responses for a game or game object
struct Interaction {
// The first few event types depend on the item - ID's of 100+ are
// custom events (used for subroutines)
InterEvtVector Events;
Interaction();
Interaction(const Interaction &inter);
// Copy information on number of times events of this interaction were fired
void CopyTimesRun(const Interaction &inter);
void Reset();
// Game static data (de)serialization
static Interaction *CreateFromStream(Stream *in);
void Write(Stream *out) const;
// Reading and writing runtime data from/to savedgame;
// NOTE: these are backwards-compatible methods, that do not always
// have practical sense
void ReadFromSavedgame_v321(Stream *in);
void WriteToSavedgame_v321(Stream *out) const;
void ReadTimesRunFromSave_v321(Stream *in);
void WriteTimesRunToSave_v321(Stream *out) const;
Interaction &operator =(const Interaction &inter);
};
typedef std::shared_ptr<Interaction> PInteraction;
// Legacy pre-3.0 kind of global and local room variables
struct InteractionVariable {
String Name{};
char Type{ '\0' };
int Value{ 0 };
InteractionVariable();
InteractionVariable(const String &name, char type, int val);
void Read(Stream *in);
void Write(Stream *out) const;
};
typedef std::vector<InteractionVariable> InterVarVector;
// A list of script function names for all supported events
struct InteractionScripts {
StringV ScriptFuncNames;
static InteractionScripts *CreateFromStream(Stream *in);
};
typedef std::shared_ptr<InteractionScripts> PInteractionScripts;
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,943 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/ac/audio_clip_type.h"
#include "ags/shared/ac/dialog_topic.h"
#include "ags/shared/ac/game_setup_struct.h"
#include "ags/shared/ac/sprite_cache.h"
#include "ags/shared/ac/view.h"
#include "ags/shared/ac/words_dictionary.h"
#include "ags/shared/ac/dynobj/script_audio_clip.h"
#include "ags/shared/core/asset.h"
#include "ags/shared/core/asset_manager.h"
#include "ags/shared/debugging/out.h"
#include "ags/shared/game/main_game_file.h"
#include "ags/shared/gui/gui_button.h"
#include "ags/shared/gui/gui_label.h"
#include "ags/shared/font/fonts.h"
#include "ags/shared/gui/gui_main.h"
#include "ags/shared/script/cc_common.h"
#include "ags/shared/util/data_ext.h"
#include "ags/shared/util/path.h"
#include "ags/shared/util/string_compat.h"
#include "ags/shared/util/string_utils.h"
#include "ags/globals.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
const char *MainGameSource::DefaultFilename_v3 = "game28.dta";
const char *MainGameSource::DefaultFilename_v2 = "ac2game.dta";
const char *MainGameSource::Signature = "Adventure Creator Game File v2";
MainGameSource::MainGameSource()
: DataVersion(kGameVersion_Undefined) {
}
String GetMainGameFileErrorText(MainGameFileErrorType err) {
switch (err) {
case kMGFErr_NoError:
return "No error.";
case kMGFErr_FileOpenFailed:
return "Main game file not found or could not be opened.";
case kMGFErr_SignatureFailed:
return "Not an AGS main game file or unsupported format.";
case kMGFErr_FormatVersionTooOld:
return "Format version is too old; this engine can only run games made with AGS 2.5 or later.";
case kMGFErr_FormatVersionNotSupported:
return "Format version not supported.";
case kMGFErr_CapsNotSupported:
return "The game requires extended capabilities which aren't supported by the engine.";
case kMGFErr_InvalidNativeResolution:
return "Unable to determine native game resolution.";
case kMGFErr_TooManySprites:
return "Too many sprites for this engine to handle.";
case kMGFErr_InvalidPropertySchema:
return "Failed to deserialize custom properties schema.";
case kMGFErr_InvalidPropertyValues:
return "Errors encountered when reading custom properties.";
case kMGFErr_CreateGlobalScriptFailed:
return "Failed to load global script.";
case kMGFErr_CreateDialogScriptFailed:
return "Failed to load dialog script.";
case kMGFErr_CreateScriptModuleFailed:
return "Failed to load script module.";
case kMGFErr_GameEntityFailed:
return "Failed to load one or more game entities.";
case kMGFErr_PluginDataFmtNotSupported:
return "Format version of plugin data is not supported.";
case kMGFErr_PluginDataSizeTooLarge:
return "Plugin data size is too large.";
case kMGFErr_ExtListFailed:
return "There was error reading game data extensions.";
case kMGFErr_ExtUnknown:
return "Unknown extension.";
default:
break;
}
return "Unknown error.";
}
LoadedGameEntities::LoadedGameEntities(GameSetupStruct &game)
: Game(game)
, SpriteCount(0) {
}
LoadedGameEntities::~LoadedGameEntities() {}
bool IsMainGameLibrary(const String &filename) {
// We must not only detect if the given file is a correct AGS data library,
// we also have to assure that this library contains main game asset.
// Library may contain some optional data (digital audio, speech etc), but
// that is not what we want.
AssetLibInfo lib;
if (AssetManager::ReadDataFileTOC(filename, lib) != kAssetNoError)
return false;
for (size_t i = 0; i < lib.AssetInfos.size(); ++i) {
if (lib.AssetInfos[i].FileName.CompareNoCase(MainGameSource::DefaultFilename_v3) == 0 ||
lib.AssetInfos[i].FileName.CompareNoCase(MainGameSource::DefaultFilename_v2) == 0) {
return true;
}
}
return false;
}
// Scans given directory for game data libraries, returns first found or none.
// Tracks files with standard AGS package names:
// - *.ags is a standart cross-platform file pattern for AGS games,
// - ac2game.dat is a legacy file name for very old games,
// - agsgame.dat is a legacy file name used in some non-Windows releases,
// - *.exe is a MS Win executable; it is included to this case because
// users often run AGS ports with Windows versions of games.
String FindGameData(const String &path, bool(*fn_testfile)(const String &)) {
Common::FSNode folder(path.GetCStr());
Common::FSList files;
if (folder.getChildren(files, Common::FSNode::kListFilesOnly)) {
for (Common::FSList::iterator it = files.begin(); it != files.end(); ++it) {
Common::String test_file = it->getName();
Common::Path filePath = it->getPath();
if (test_file.hasSuffixIgnoreCase(".ags") ||
test_file.equalsIgnoreCase("ac2game.dat") ||
test_file.equalsIgnoreCase("agsgame.dat") ||
test_file.hasSuffixIgnoreCase(".exe")) {
if (IsMainGameLibrary(test_file.c_str()) && fn_testfile(filePath.toString('/'))) {
Debug::Printf("Found game data pak: %s", test_file.c_str());
return test_file.c_str();
}
}
}
}
return "";
}
static bool comparitor(const String &) {
return true;
}
String FindGameData(const String &path) {
return FindGameData(path, comparitor);
}
// Begins reading main game file from a generic stream
static HGameFileError OpenMainGameFileBase(Stream *in, MainGameSource &src) {
// Check data signature
String data_sig = String::FromStreamCount(in, strlen(MainGameSource::Signature));
if (data_sig.Compare(MainGameSource::Signature))
return new MainGameFileError(kMGFErr_SignatureFailed);
// Read data format version and requested engine version
src.DataVersion = (GameDataVersion)in->ReadInt32();
if (src.DataVersion >= kGameVersion_230)
src.CompiledWith = StrUtil::ReadString(in);
if (src.DataVersion < kGameVersion_250)
return new MainGameFileError(kMGFErr_FormatVersionTooOld, String::FromFormat("Required format version: %d, supported %d - %d", src.DataVersion, kGameVersion_250, kGameVersion_Current));
if (src.DataVersion > kGameVersion_Current)
return new MainGameFileError(kMGFErr_FormatVersionNotSupported,
String::FromFormat("Game was compiled with %s. Required format version: %d, supported %d - %d", src.CompiledWith.GetCStr(), src.DataVersion, kGameVersion_250, kGameVersion_Current));
// Read required capabilities
if (src.DataVersion >= kGameVersion_341) {
size_t count = in->ReadInt32();
for (size_t i = 0; i < count; ++i)
src.Caps.insert(StrUtil::ReadString(in));
}
// Remember loaded game data version
// NOTE: this global variable is embedded in the code too much to get
// rid of it too easily; the easy way is to set it whenever the main
// game file is opened.
_G(loaded_game_file_version) = src.DataVersion;
_G(game_compiled_version).SetFromString(src.CompiledWith);
return HGameFileError::None();
}
HGameFileError OpenMainGameFile(const String &filename, MainGameSource &src) {
// Cleanup source struct
src = MainGameSource();
// Try to open given file
Stream *in = File::OpenFileRead(filename);
if (!in)
return new MainGameFileError(kMGFErr_FileOpenFailed, String::FromFormat("Tried filename: %s.", filename.GetCStr()));
src.Filename = filename;
src.InputStream.reset(in);
return OpenMainGameFileBase(in, src);
}
HGameFileError OpenMainGameFileFromDefaultAsset(MainGameSource &src, AssetManager *mgr) {
// Cleanup source struct
src = MainGameSource();
// Try to find and open main game file
String filename = MainGameSource::DefaultFilename_v3;
Stream *in = mgr->OpenAsset(filename);
if (!in) {
filename = MainGameSource::DefaultFilename_v2;
in = mgr->OpenAsset(filename);
}
if (!in)
return new MainGameFileError(kMGFErr_FileOpenFailed,
String::FromFormat("Tried filenames: %s, %s.", MainGameSource::DefaultFilename_v3, MainGameSource::DefaultFilename_v2));
src.Filename = filename;
src.InputStream.reset(in);
return OpenMainGameFileBase(in, src);
}
HGameFileError ReadDialogScript(PScript &dialog_script, Stream *in, GameDataVersion data_ver) {
if (data_ver > kGameVersion_310) { // 3.1.1+ dialog script
dialog_script.reset(ccScript::CreateFromStream(in));
if (dialog_script == nullptr)
return new MainGameFileError(kMGFErr_CreateDialogScriptFailed, cc_get_error().ErrorString);
} else { // 2.x and < 3.1.1 dialog
dialog_script.reset();
}
return HGameFileError::None();
}
HGameFileError ReadScriptModules(std::vector<PScript> &sc_mods, Stream *in, GameDataVersion data_ver) {
if (data_ver >= kGameVersion_270) { // 2.7.0+ script modules
int count = in->ReadInt32();
sc_mods.resize(count);
for (int i = 0; i < count; ++i) {
sc_mods[i].reset(ccScript::CreateFromStream(in));
if (sc_mods[i] == nullptr)
return new MainGameFileError(kMGFErr_CreateScriptModuleFailed, cc_get_error().ErrorString);
}
} else {
sc_mods.resize(0);
}
return HGameFileError::None();
}
void ReadViews(GameSetupStruct &game, std::vector<ViewStruct> &views, Stream *in, GameDataVersion data_ver) {
views.resize(game.numviews);
if (data_ver > kGameVersion_272) // 3.x views
{
for (int i = 0; i < game.numviews; ++i) {
views[i].ReadFromFile(in);
}
} else // 2.x views
{
std::vector<ViewStruct272> oldv(game.numviews);
for (int i = 0; i < game.numviews; ++i) {
oldv[i].ReadFromFile(in);
}
Convert272ViewsToNew(oldv, views);
}
}
void ReadDialogs(std::vector<DialogTopic> &dialog,
std::vector<std::vector<uint8_t>> &old_dialog_scripts,
std::vector<String> &old_dialog_src,
std::vector<String> &old_speech_lines,
Stream *in, GameDataVersion data_ver, int dlg_count) {
dialog.resize(dlg_count);
for (int i = 0; i < dlg_count; ++i) {
dialog[i].ReadFromFile(in);
}
if (data_ver > kGameVersion_310)
return;
old_dialog_scripts.resize(dlg_count);
old_dialog_src.resize(dlg_count);
for (int i = 0; i < dlg_count; ++i) {
// NOTE: originally this was read into dialog[i].optionscripts
old_dialog_scripts[i].resize(dialog[i].codesize);
in->Read(old_dialog_scripts[i].data(), dialog[i].codesize);
// Encrypted text script
int script_text_len = in->ReadInt32();
if (script_text_len > 1) {
// Originally in the Editor +20000 bytes more were allocated, with comment:
// "add a large buffer because it will get added to if another option is added"
// which probably referred to this data used by old editor directly to edit dialogs
char *buffer = new char[script_text_len + 1];
in->Read(buffer, script_text_len);
if (data_ver > kGameVersion_260)
decrypt_text(buffer, script_text_len);
buffer[script_text_len] = 0;
old_dialog_src[i] = buffer;
delete[] buffer;
} else {
in->Seek(script_text_len);
}
}
// Read the dialog lines
//
// TODO: investigate this: these strings were read much simpler in the editor, see code:
/*
char stringbuffer[1000];
for (bb=0;bb<thisgame.numdlgmessage;bb++) {
if ((filever >= 26) && (encrypted))
read_string_decrypt(iii, stringbuffer);
else
fgetstring(stringbuffer, iii);
}
*/
//int i = 0;
char buffer[1000];
if (data_ver <= kGameVersion_260) {
// Plain text on <= 2.60
bool end_reached = false;
while (!end_reached) {
char *nextchar = buffer;
while (1) {
*nextchar = in->ReadInt8();
if (*nextchar == 0)
break;
if ((unsigned char)*nextchar == 0xEF) {
end_reached = true;
in->Seek(-1);
break;
}
nextchar++;
}
if (end_reached)
break;
old_speech_lines.push_back(buffer);
//i++;
}
} else {
// Encrypted text on > 2.60
while (1) {
size_t newlen = static_cast<uint32_t>(in->ReadInt32());
if (newlen == 0xCAFEBEEF) { // GUI magic
in->Seek(-4);
break;
}
newlen = MIN(newlen, sizeof(buffer) - 1);
in->Read(buffer, newlen);
decrypt_text(buffer, newlen);
buffer[newlen] = 0;
old_speech_lines.push_back(buffer);
//i++;
}
}
}
HGameFileError ReadPlugins(std::vector<PluginInfo> &infos, Stream *in) {
int fmt_ver = in->ReadInt32();
if (fmt_ver != 1)
return new MainGameFileError(kMGFErr_PluginDataFmtNotSupported, String::FromFormat("Version: %d, supported: %d", fmt_ver, 1));
int pl_count = in->ReadInt32();
for (int i = 0; i < pl_count; ++i) {
String name = String::FromStream(in);
size_t datasize = in->ReadInt32();
// just check for silly datasizes
if (datasize > PLUGIN_SAVEBUFFERSIZE)
return new MainGameFileError(kMGFErr_PluginDataSizeTooLarge, String::FromFormat("Required: %zu, max: %zu", datasize, (size_t)PLUGIN_SAVEBUFFERSIZE));
PluginInfo info;
info.Name = name;
if (datasize > 0) {
info.Data.resize(datasize);
in->Read(&info.Data.front(), datasize);
}
infos.push_back(info);
}
return HGameFileError::None();
}
// Create the missing audioClips data structure for 3.1.x games.
// This is done by going through the data files and adding all music*.*
// and sound*.* files to it.
void BuildAudioClipArray(const std::vector<String> &assets, std::vector<ScriptAudioClip> &audioclips) {
char temp_name[30];
int temp_number;
char temp_extension[10];
for (const String &asset : assets) {
if (sscanf(asset.GetCStr(), "%5s%d.%3s", temp_name, &temp_number, temp_extension) != 3)
continue;
ScriptAudioClip clip;
if (ags_stricmp(temp_extension, "mp3") == 0)
clip.fileType = eAudioFileMP3;
else if (ags_stricmp(temp_extension, "wav") == 0)
clip.fileType = eAudioFileWAV;
else if (ags_stricmp(temp_extension, "voc") == 0)
clip.fileType = eAudioFileVOC;
else if (ags_stricmp(temp_extension, "mid") == 0)
clip.fileType = eAudioFileMIDI;
else if ((ags_stricmp(temp_extension, "mod") == 0) || (ags_stricmp(temp_extension, "xm") == 0)
|| (ags_stricmp(temp_extension, "s3m") == 0) || (ags_stricmp(temp_extension, "it") == 0))
clip.fileType = eAudioFileMOD;
else if (ags_stricmp(temp_extension, "ogg") == 0)
clip.fileType = eAudioFileOGG;
else
continue;
if (ags_stricmp(temp_name, "music") == 0) {
clip.scriptName.Format("aMusic%d", temp_number);
clip.fileName.Format("music%d.%s", temp_number, temp_extension);
clip.bundlingType = (ags_stricmp(temp_extension, "mid") == 0) ? AUCL_BUNDLE_EXE : AUCL_BUNDLE_VOX;
clip.type = 2;
clip.defaultRepeat = 1;
} else if (ags_stricmp(temp_name, "sound") == 0) {
clip.scriptName.Format("aSound%d", temp_number);
clip.fileName.Format("sound%d.%s", temp_number, temp_extension);
clip.bundlingType = AUCL_BUNDLE_EXE;
clip.type = 3;
clip.defaultRepeat = 0;
} else {
continue;
}
clip.defaultVolume = 100;
clip.defaultPriority = 50;
clip.id = audioclips.size();
audioclips.push_back(clip);
}
}
void ApplySpriteData(GameSetupStruct &game, const LoadedGameEntities &ents, GameDataVersion data_ver) {
if (ents.SpriteCount == 0)
return;
// Apply sprite flags read from original format (sequential array)
_GP(spriteset).EnlargeTo(ents.SpriteCount - 1);
for (size_t i = 0; i < ents.SpriteCount; ++i) {
_GP(game).SpriteInfos[i].Flags = ents.SpriteFlags[i];
}
// Promote sprite resolutions and mark legacy resolution setting
if (data_ver < kGameVersion_350) {
for (size_t i = 0; i < ents.SpriteCount; ++i) {
SpriteInfo &info = _GP(game).SpriteInfos[i];
if (_GP(game).IsLegacyHiRes() == info.IsLegacyHiRes())
info.Flags &= ~(SPF_HIRES | SPF_VAR_RESOLUTION);
else
info.Flags |= SPF_VAR_RESOLUTION;
}
}
}
void UpgradeFonts(GameSetupStruct &game, GameDataVersion data_ver) {
if (data_ver < kGameVersion_350) {
for (int i = 0; i < _GP(game).numfonts; ++i) {
FontInfo &finfo = _GP(game).fonts[i];
// If the game is hi-res but font is designed for low-res, then scale it up
if (_GP(game).IsLegacyHiRes() && _GP(game).options[OPT_HIRES_FONTS] == 0) {
finfo.SizeMultiplier = HIRES_COORD_MULTIPLIER;
} else {
finfo.SizeMultiplier = 1;
}
}
}
if (data_ver < kGameVersion_360) {
for (int i = 0; i < game.numfonts; ++i) {
FontInfo &finfo = game.fonts[i];
if (finfo.Outline == FONT_OUTLINE_AUTO) {
finfo.AutoOutlineStyle = FontInfo::kSquared;
finfo.AutoOutlineThickness = 1;
}
}
}
if (data_ver < kGameVersion_360_11) { // use global defaults for the font load flags
for (int i = 0; i < game.numfonts; ++i) {
game.fonts[i].Flags |= FFLG_TTF_BACKCOMPATMASK;
}
}
}
// Convert audio data to the current version
void UpgradeAudio(GameSetupStruct &game, LoadedGameEntities &ents, GameDataVersion data_ver) {
if (data_ver >= kGameVersion_320)
return;
// An explanation of building audio clips array for pre-3.2 games.
//
// When AGS version 3.2 was released, it contained new audio system.
// In the nutshell, prior to 3.2 audio files had to be manually put
// to game project directory and their IDs were taken out of filenames.
// Since 3.2 this information is stored inside the game data.
// To make the modern engine compatible with pre-3.2 games, we have
// to scan game data packages for audio files, and enumerate them
// ourselves, then add this information to game struct.
// Create soundClips and audioClipTypes structures.
std::vector<AudioClipType> audiocliptypes;
std::vector<ScriptAudioClip> audioclips;
// TODO: find out what is 4 (maybe music, sound, ambient sound, voice?)
audiocliptypes.resize(4);
for (int i = 0; i < 4; i++) {
audiocliptypes[i].reservedChannels = 1;
audiocliptypes[i].id = i;
audiocliptypes[i].volume_reduction_while_speech_playing = 10;
}
audiocliptypes[3].reservedChannels = 0;
audioclips.reserve(1000);
std::vector<String> assets;
// Read audio clip names from from registered libraries
for (size_t i = 0; i < _GP(AssetMgr)->GetLibraryCount(); ++i) {
const AssetLibInfo *game_lib = _GP(AssetMgr)->GetLibraryInfo(i);
if (File::IsDirectory(game_lib->BasePath))
continue; // might be a directory
for (const AssetInfo &info : game_lib->AssetInfos) {
if (info.FileName.CompareLeftNoCase("music", 5) == 0 || info.FileName.CompareLeftNoCase("sound", 5) == 0)
assets.push_back(info.FileName);
}
}
// Append contents of the registered directories
// TODO: implement pattern search or asset query with callback (either of two or both)
// within AssetManager to avoid doing this in place here. Alternatively we could maybe
// make AssetManager to do directory scans by demand and fill AssetInfos...
// but that have to be done consistently if done at all.
for (size_t i = 0; i < _GP(AssetMgr)->GetLibraryCount(); ++i) {
const AssetLibInfo *game_lib = _GP(AssetMgr)->GetLibraryInfo(i);
if (!File::IsDirectory(game_lib->BasePath))
continue; // might be a library
Common::FSNode folder(game_lib->BasePath.GetCStr());
Common::FSList files;
folder.getChildren(files, Common::FSNode::kListFilesOnly);
for (Common::FSList::iterator it = files.begin(); it != files.end(); ++it) {
Common::String name = (*it).getName();
if (name.hasPrefixIgnoreCase("music") || name.hasPrefixIgnoreCase("sound"))
assets.push_back(name.c_str());
}
}
BuildAudioClipArray(assets, audioclips);
// Copy gathered data over to game
_GP(game).audioClipTypes = audiocliptypes;
_GP(game).audioClips = audioclips;
RemapLegacySoundNums(game, ents.Views, data_ver);
}
// Convert character data to the current version
void UpgradeCharacters(GameSetupStruct &game, GameDataVersion data_ver) {
auto &chars = _GP(game).chars;
auto &chars2 = _GP(game).chars2;
const int numcharacters = _GP(game).numcharacters;
// Fixup character script names for 2.x (EGO -> cEgo)
// In 2.x versions the "scriptname" field in game data contained a name
// limited by 14 chars (although serialized in 20 bytes). After reading,
// it was exported as "cScriptname..." for the script.
if (data_ver <= kGameVersion_272) {
char namelwr[LEGACY_MAX_SCRIPT_NAME_LEN - 1];
for (int i = 0; i < numcharacters; i++) {
if (chars[i].scrname[0] == 0)
continue;
ags_strncpy_s(namelwr, sizeof(namelwr), chars[i].scrname, LEGACY_MAX_SCRIPT_NAME_LEN - 2);
ags_strlwr(namelwr + 1); // lowercase starting with the second char
snprintf(chars[i].scrname, sizeof(chars[i].scrname), "c%s", namelwr);
chars2[i].scrname_new = chars[i].scrname;
}
}
// Fix character walk speed for < 3.1.1
if (data_ver <= kGameVersion_310) {
for (int i = 0; i < numcharacters; i++) {
if (_GP(game).options[OPT_ANTIGLIDE])
chars[i].flags |= CHF_ANTIGLIDE;
}
}
// Characters can always walk through each other on < 2.54
if (data_ver < kGameVersion_254) {
for (int i = 0; i < numcharacters; i++) {
chars[i].flags |= CHF_NOBLOCKING;
}
}
}
void UpgradeGUI(GameSetupStruct &game, GameDataVersion data_ver) {
// Previously, Buttons and Labels had a fixed Translated behavior
if (data_ver < kGameVersion_361) {
for (auto &btn : _GP(guibuts))
btn.SetTranslated(true); // always translated
for (auto &lbl : _GP(guilabels))
lbl.SetTranslated(true); // always translated
}
}
void UpgradeMouseCursors(GameSetupStruct &game, GameDataVersion data_ver) {
if (data_ver <= kGameVersion_272) {
// Change cursor.view from 0 to -1 for non-animating cursors.
for (int i = 0; i < _GP(game).numcursors; ++i) {
if (_GP(game).mcurs[i].view == 0)
_GP(game).mcurs[i].view = -1;
}
}
}
// Adjusts score clip id, depending on game data version
void RemapLegacySoundNums(GameSetupStruct &game, std::vector<ViewStruct> &views, GameDataVersion data_ver) {
if (data_ver >= kGameVersion_320)
return;
// Setup sound clip played on score event
game.scoreClipID = -1;
if (game.options[OPT_SCORESOUND] > 0) {
ScriptAudioClip *clip = GetAudioClipForOldStyleNumber(game, false, game.options[OPT_SCORESOUND]);
if (clip)
game.scoreClipID = clip->id;
}
// Reset view frame clip refs
// NOTE: we do not map these to real clips right away,
// instead we do this at runtime whenever we find a non-mapped frame sound.
for (size_t v = 0; v < (size_t)game.numviews; ++v) {
for (size_t l = 0; l < (size_t)views[v].numLoops; ++l) {
for (size_t f = 0; f < (size_t)views[v].loops[l].numFrames; ++f) {
views[v].loops[l].frames[f].audioclip = -1;
}
}
}
}
// Assigns default global message at given index
void SetDefaultGlmsg(GameSetupStruct &game, int msgnum, const char *val) {
// TODO: find out why the index should be lowered by 500
// (or rather if we may pass correct index right away)
msgnum -= 500;
if (_GP(game).messages[msgnum].IsEmpty())
_GP(game).messages[msgnum] = val;
}
// Sets up default global messages (these are used mainly in older games)
void SetDefaultGlobalMessages(GameSetupStruct &game) {
SetDefaultGlmsg(game, 983, "Sorry, not now.");
SetDefaultGlmsg(game, 984, "Restore");
SetDefaultGlmsg(game, 985, "Cancel");
SetDefaultGlmsg(game, 986, "Select a game to restore:");
SetDefaultGlmsg(game, 987, "Save");
SetDefaultGlmsg(game, 988, "Type a name to save as:");
SetDefaultGlmsg(game, 989, "Replace");
SetDefaultGlmsg(game, 990, "The save directory is full. You must replace an existing game:");
SetDefaultGlmsg(game, 991, "Replace:");
SetDefaultGlmsg(game, 992, "With:");
SetDefaultGlmsg(game, 993, "Quit");
SetDefaultGlmsg(game, 994, "Play");
SetDefaultGlmsg(game, 995, "Are you sure you want to quit?");
SetDefaultGlmsg(game, 996, "You are carrying nothing.");
}
void FixupSaveDirectory(GameSetupStruct &game) {
// If the save game folder was not specified by game author, create one of
// the game name, game GUID, or uniqueid, as a last resort
if (_GP(game).saveGameFolderName.IsEmpty()) {
if (!_GP(game).gamename.IsEmpty())
_GP(game).saveGameFolderName = _GP(game).gamename;
else if (_GP(game).guid[0])
_GP(game).saveGameFolderName = _GP(game).guid;
else
_GP(game).saveGameFolderName.Format("AGS-Game-%d", _GP(game).uniqueid);
}
// Lastly, fixup folder name by removing any illegal characters
_GP(game).saveGameFolderName = Path::FixupSharedFilename(_GP(game).saveGameFolderName);
}
HGameFileError ReadSpriteFlags(LoadedGameEntities &ents, Stream *in, GameDataVersion data_ver) {
size_t sprcount;
if (data_ver < kGameVersion_256)
sprcount = LEGACY_MAX_SPRITES_V25;
else
sprcount = in->ReadInt32();
if (sprcount > (size_t)SpriteCache::MAX_SPRITE_INDEX + 1)
return new MainGameFileError(kMGFErr_TooManySprites, String::FromFormat("Count: %zu, max: %zu", sprcount, (size_t)SpriteCache::MAX_SPRITE_INDEX + 1));
ents.SpriteCount = sprcount;
ents.SpriteFlags.resize(sprcount);
in->Read(ents.SpriteFlags.data(), sprcount);
return HGameFileError::None();
}
// GameDataExtReader reads main game data's extension blocks
class GameDataExtReader : public DataExtReader {
public:
GameDataExtReader(LoadedGameEntities &ents, GameDataVersion data_ver, Stream *in)
: DataExtReader(in, kDataExt_NumID8 | kDataExt_File64)
, _ents(ents)
, _dataVer(data_ver) {
}
protected:
HError ReadBlock(int block_id, const String &ext_id,
soff_t block_len, bool &read_next) override;
LoadedGameEntities &_ents;
GameDataVersion _dataVer;
};
HError GameDataExtReader::ReadBlock(int /*block_id*/, const String &ext_id,
soff_t /*block_len*/, bool &read_next) {
read_next = true;
// Add extensions here checking ext_id, which is an up to 16-chars name, for example:
// if (ext_id.CompareNoCase("GUI_NEWPROPS") == 0)
// {
// // read new gui properties
// }
if (ext_id.CompareNoCase("v360_fonts") == 0) {
for (FontInfo &finfo : _ents.Game.fonts) {
// adjustable font outlines
finfo.AutoOutlineThickness = _in->ReadInt32();
finfo.AutoOutlineStyle = static_cast<enum FontInfo::AutoOutlineStyle>(_in->ReadInt32());
// reserved
_in->ReadInt32();
_in->ReadInt32();
_in->ReadInt32();
_in->ReadInt32();
}
} else if (ext_id.CompareNoCase("v360_cursors") == 0) {
for (MouseCursor &mcur : _ents.Game.mcurs) {
mcur.animdelay = _in->ReadInt32();
// reserved
_in->ReadInt32();
_in->ReadInt32();
_in->ReadInt32();
}
} else if (ext_id.CompareNoCase("v361_objnames") == 0) {
// Extended object names and script names:
// for object types that had hard name length limits
_ents.Game.gamename = StrUtil::ReadString(_in);
_ents.Game.saveGameFolderName = StrUtil::ReadString(_in);
size_t num_chars = _in->ReadInt32();
if (num_chars != _ents.Game.chars.size())
return new Error(String::FromFormat("Mismatching number of characters: read %zu expected %zu", num_chars, _ents.Game.chars.size()));
for (int i = 0; i < _ents.Game.numcharacters; ++i) {
auto &chinfo = _ents.Game.chars[i];
auto &chinfo2 = _ents.Game.chars2[i];
chinfo2.scrname_new = StrUtil::ReadString(_in);
chinfo2.name_new = StrUtil::ReadString(_in);
// assign to the legacy fields for compatibility with old plugins
snprintf(chinfo.scrname, LEGACY_MAX_SCRIPT_NAME_LEN, "%s", chinfo2.scrname_new.GetCStr());
snprintf(chinfo.name, LEGACY_MAX_CHAR_NAME_LEN, "%s", chinfo2.name_new.GetCStr());
}
size_t num_invitems = _in->ReadInt32();
if (num_invitems != (size_t)_ents.Game.numinvitems)
return new Error(String::FromFormat("Mismatching number of inventory items: read %zu expected %zu", num_invitems, (size_t)_ents.Game.numinvitems));
for (int i = 0; i < _ents.Game.numinvitems; ++i) {
_ents.Game.invinfo[i].name = StrUtil::ReadString(_in);
}
size_t num_cursors = _in->ReadInt32();
if (num_cursors != _ents.Game.mcurs.size())
return new Error(String::FromFormat("Mismatching number of cursors: read %zu expected %zu", num_cursors, _ents.Game.mcurs.size()));
for (MouseCursor &mcur : _ents.Game.mcurs) {
mcur.name = StrUtil::ReadString(_in);
}
size_t num_clips = _in->ReadInt32();
if (num_clips != _ents.Game.audioClips.size())
return new Error(String::FromFormat("Mismatching number of audio clips: read %zu expected %zu", num_clips, _ents.Game.audioClips.size()));
for (ScriptAudioClip &clip : _ents.Game.audioClips) {
clip.scriptName = StrUtil::ReadString(_in);
clip.fileName = StrUtil::ReadString(_in);
}
} else {
return new MainGameFileError(kMGFErr_ExtUnknown, String::FromFormat("Type: %s", ext_id.GetCStr()));
}
return HError::None();
}
// Search and read only data belonging to the general game info
class GameDataExtPreloader : public GameDataExtReader {
public:
GameDataExtPreloader(LoadedGameEntities &ents, GameDataVersion data_ver, Stream *in)
: GameDataExtReader(ents, data_ver, in) {}
protected:
HError ReadBlock(int block_id, const String &ext_id, soff_t block_len, bool &read_next) override;
};
HError GameDataExtPreloader::ReadBlock(int /*block_id*/, const String &ext_id,
soff_t /*block_len*/, bool &read_next) {
// Try reading only data which belongs to the general game info
read_next = true;
if (ext_id.CompareNoCase("v361_objnames") == 0) {
_ents.Game.gamename = StrUtil::ReadString(_in);
_ents.Game.saveGameFolderName = StrUtil::ReadString(_in);
read_next = false; // we're done
}
SkipBlock(); // prevent assertion trigger
return HError::None();
}
HGameFileError ReadGameData(LoadedGameEntities &ents, Stream *in, GameDataVersion data_ver) {
GameSetupStruct &game = ents.Game;
//-------------------------------------------------------------------------
// The standard data section.
//-------------------------------------------------------------------------
GameSetupStruct::SerializeInfo sinfo;
game.GameSetupStructBase::ReadFromFile(in, data_ver, sinfo);
game.read_savegame_info(in, data_ver); // here we also read GUID in v3.* games
Debug::Printf(kDbgMsg_Info, "Game title: '%s'", game.gamename.GetCStr());
Debug::Printf(kDbgMsg_Info, "Game uid (old format): `%d`", game.uniqueid);
Debug::Printf(kDbgMsg_Info, "Game guid: '%s'", game.guid);
if (game.GetGameRes().IsNull())
return new MainGameFileError(kMGFErr_InvalidNativeResolution);
game.read_font_infos(in, data_ver);
HGameFileError err = ReadSpriteFlags(ents, in, data_ver);
if (!err)
return err;
game.ReadInvInfo(in);
err = game.read_cursors(in);
if (!err)
return err;
game.read_interaction_scripts(in, data_ver);
if (sinfo.HasWordsDict)
game.read_words_dictionary(in);
if (sinfo.HasCCScript) {
ents.GlobalScript.reset(ccScript::CreateFromStream(in));
if (!ents.GlobalScript)
return new MainGameFileError(kMGFErr_CreateGlobalScriptFailed, cc_get_error().ErrorString);
err = ReadDialogScript(ents.DialogScript, in, data_ver);
if (!err)
return err;
err = ReadScriptModules(ents.ScriptModules, in, data_ver);
if (!err)
return err;
}
ReadViews(game, ents.Views, in, data_ver);
if (data_ver <= kGameVersion_251) {
// skip unknown data
int count = in->ReadInt32();
in->Seek(count * 0x204);
}
game.read_characters(in);
game.read_lipsync(in, data_ver);
game.read_messages(in, sinfo.HasMessages, data_ver);
ReadDialogs(ents.Dialogs, ents.OldDialogScripts, ents.OldDialogSources, ents.OldSpeechLines,
in, data_ver, game.numdialog);
HError err2 = GUI::ReadGUI(in);
if (!err2)
return new MainGameFileError(kMGFErr_GameEntityFailed, err2);
game.numgui = _GP(guis).size();
if (data_ver >= kGameVersion_260) {
err = ReadPlugins(ents.PluginInfos, in);
if (!err)
return err;
}
err = game.read_customprops(in, data_ver);
if (!err)
return err;
err = game.read_audio(in, data_ver);
if (!err)
return err;
game.read_room_names(in, data_ver);
if (data_ver <= kGameVersion_350)
return HGameFileError::None();
//-------------------------------------------------------------------------
// All the extended data, for AGS > 3.5.0.
//-------------------------------------------------------------------------
GameDataExtReader reader(ents, data_ver, in);
HError ext_err = reader.Read();
return ext_err ? HGameFileError::None() : new MainGameFileError(kMGFErr_ExtListFailed, ext_err);
}
HGameFileError UpdateGameData(LoadedGameEntities &ents, GameDataVersion data_ver) {
GameSetupStruct &game = ents.Game;
ApplySpriteData(game, ents, data_ver);
UpgradeFonts(game, data_ver);
UpgradeAudio(game, ents, data_ver);
UpgradeCharacters(game, data_ver);
UpgradeGUI(game, data_ver);
UpgradeMouseCursors(game, data_ver);
SetDefaultGlobalMessages(game);
// Global talking animation speed
if (data_ver < kGameVersion_312) {
// Fix animation speed for old formats
game.options[OPT_GLOBALTALKANIMSPD] = 5;
} else if (data_ver < kGameVersion_330) {
// Convert game option for 3.1.2 - 3.2 games
game.options[OPT_GLOBALTALKANIMSPD] = game.options[OPT_GLOBALTALKANIMSPD] != 0 ? 5 : (-5 - 1);
}
// Old dialog options API for pre-3.4.0.2 games
if (data_ver < kGameVersion_340_2) {
game.options[OPT_DIALOGOPTIONSAPI] = -1;
}
// Relative asset resolution in pre-3.5.0.8 (always enabled)
if (data_ver < kGameVersion_350) {
game.options[OPT_RELATIVEASSETRES] = 1;
}
FixupSaveDirectory(game);
return HGameFileError::None();
}
void PreReadGameData(GameSetupStruct &game, Stream *in, GameDataVersion data_ver) {
GameSetupStruct::SerializeInfo sinfo;
_GP(game).GameSetupStructBase::ReadFromFile(in, data_ver, sinfo);
_GP(game).read_savegame_info(in, data_ver); // here we also read GUID in v3.* games
// Check for particular expansions that might have data necessary
// for "preload" purposes
if (sinfo.ExtensionOffset == 0u)
return; // either no extensions, or data version is too early
in->Seek(sinfo.ExtensionOffset, kSeekBegin);
LoadedGameEntities ents(game);
GameDataExtPreloader reader(ents, data_ver, in);
reader.Read();
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,165 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// This unit provides functions for reading main game file into appropriate
// data structures. Main game file contains general game data, such as global
// options, lists of static game entities and compiled scripts modules.
//
//=============================================================================
#ifndef AGS_SHARED_GAME_MAIN_GAME_FILE_H
#define AGS_SHARED_GAME_MAIN_GAME_FILE_H
#include "common/std/functional.h"
#include "common/std/memory.h"
#include "common/std/set.h"
#include "common/std/vector.h"
#include "ags/shared/core/platform.h"
#include "ags/shared/ac/game_version.h"
#include "ags/shared/ac/view.h"
#include "ags/shared/game/plugin_info.h"
#include "ags/shared/script/cc_script.h"
#include "ags/shared/util/error.h"
#include "ags/shared/util/stream.h"
#include "ags/shared/util/string.h"
#include "ags/shared/util/version.h"
namespace AGS3 {
struct GameSetupStruct;
struct DialogTopic;
namespace AGS {
namespace Shared {
// Error codes for main game file reading
enum MainGameFileErrorType {
kMGFErr_NoError,
kMGFErr_FileOpenFailed,
kMGFErr_SignatureFailed,
// separate error given for "too old" format to provide clarifying message
kMGFErr_FormatVersionTooOld,
kMGFErr_FormatVersionNotSupported,
kMGFErr_CapsNotSupported,
kMGFErr_InvalidNativeResolution,
kMGFErr_TooManySprites,
kMGFErr_InvalidPropertySchema,
kMGFErr_InvalidPropertyValues,
kMGFErr_CreateGlobalScriptFailed,
kMGFErr_CreateDialogScriptFailed,
kMGFErr_CreateScriptModuleFailed,
kMGFErr_GameEntityFailed,
kMGFErr_PluginDataFmtNotSupported,
kMGFErr_PluginDataSizeTooLarge,
kMGFErr_ExtListFailed,
kMGFErr_ExtUnknown
};
String GetMainGameFileErrorText(MainGameFileErrorType err);
typedef TypedCodeError<MainGameFileErrorType, GetMainGameFileErrorText> MainGameFileError;
typedef ErrorHandle<MainGameFileError> HGameFileError;
typedef std::unique_ptr<Stream> UStream;
// MainGameSource defines a successfully opened main game file
struct MainGameSource {
// Standart main game file names for 3.* and 2.* games respectively
static const char *DefaultFilename_v3;
static const char *DefaultFilename_v2;
// Signature of the current game format
static const char *Signature;
// Name of the asset file
String Filename;
// Game file format version
GameDataVersion DataVersion;
// Tool identifier (like version) this game was compiled with
String CompiledWith;
// Extended engine capabilities required by the game; their primary use
// currently is to let "alternate" game formats indicate themselves
std::set<String> Caps;
// A ponter to the opened stream
UStream InputStream;
MainGameSource();
};
// LoadedGameEntities is meant for keeping objects loaded from the game file.
// Because copying/assignment methods are not properly implemented for some
// of these objects yet, they have to be attached using references to be read
// directly. This is temporary solution that has to be resolved by the future
// code refactoring.
struct LoadedGameEntities {
GameSetupStruct &Game;
std::vector<DialogTopic> Dialogs;
std::vector<ViewStruct> Views;
PScript GlobalScript;
PScript DialogScript;
std::vector<PScript> ScriptModules;
std::vector<PluginInfo> PluginInfos;
// Original sprite data (when it was read into const-sized arrays)
size_t SpriteCount;
std::vector<uint8_t> SpriteFlags; // SPF_* flags
// Old dialog support
// legacy compiled dialog script of its own format,
// requires separate interpreting
std::vector<std::vector<uint8_t>> OldDialogScripts;
// probably, actual dialog script sources kept within some older games
std::vector<String> OldDialogSources;
// speech texts displayed during dialog
std::vector<String> OldSpeechLines;
LoadedGameEntities(GameSetupStruct &game);
~LoadedGameEntities();
};
class AssetManager;
// Tells if the given path (library filename) contains main game file
bool IsMainGameLibrary(const String &filename);
// Scans given directory path for a package containing main game data, returns first found or none.
String FindGameData(const String &path);
String FindGameData(const String &path, bool(*fn_testfile)(const String &));
// Opens main game file for reading from an arbitrary file
HGameFileError OpenMainGameFile(const String &filename, MainGameSource &src);
// Opens main game file for reading using the current Asset Manager (uses default asset name)
HGameFileError OpenMainGameFileFromDefaultAsset(MainGameSource &src, AssetManager *mgr);
// Reads game data, applies necessary conversions to match current format version
HGameFileError ReadGameData(LoadedGameEntities &ents, Stream *in, GameDataVersion data_ver);
// Pre-reads the heading game data, just enough to identify the game and its special file locations
void PreReadGameData(GameSetupStruct &game, Stream *in, GameDataVersion data_ver);
// Applies necessary updates, conversions and fixups to the loaded data
// making it compatible with current engine
HGameFileError UpdateGameData(LoadedGameEntities &ents, GameDataVersion data_ver);
// Ensures that the game saves directory path is valid
void FixupSaveDirectory(GameSetupStruct &game);
// Maps legacy sound numbers to real audio clips
void RemapLegacySoundNums(GameSetupStruct &game, std::vector<ViewStruct> &views, GameDataVersion data_ver);
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,54 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// PluginInfo - a struct defining general information on game plugin.
//
//=============================================================================
#ifndef AGS_SHARED_GAME_PLUGIN_INFO_H
#define AGS_SHARED_GAME_PLUGIN_INFO_H
#include "common/std/memory.h"
#include "ags/shared/util/string.h"
// TODO: why 10 MB limit?
#define PLUGIN_SAVEBUFFERSIZE 10247680
namespace AGS3 {
namespace AGS {
namespace Shared {
struct PluginInfo {
// (File)name of plugin
String Name;
// Custom data for plugin
std::vector<uint8_t> Data;
PluginInfo() = default;
};
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,799 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/ac/common_defines.h"
#include "ags/shared/ac/game_struct_defines.h"
#include "ags/shared/ac/words_dictionary.h" // TODO: extract string decryption
#include "ags/shared/core/asset_manager.h"
#include "ags/shared/debugging/out.h"
#include "ags/shared/game/custom_properties.h"
#include "ags/shared/game/room_file.h"
#include "ags/shared/game/room_struct.h"
#include "ags/shared/gfx/bitmap.h"
#include "ags/shared/script/cc_common.h"
#include "ags/shared/script/cc_script.h"
#include "ags/shared/util/compress.h"
#include "ags/shared/util/data_ext.h"
#include "ags/shared/util/string_utils.h"
#include "ags/globals.h"
namespace AGS3 {
// default number of hotspots to read from the room file
#define MIN_ROOM_HOTSPOTS 20
#define LEGACY_HOTSPOT_NAME_LEN 30
#define LEGACY_ROOM_PASSWORD_LENGTH 11
#define LEGACY_ROOM_PASSWORD_SALT 60
#define ROOM_MESSAGE_FLAG_DISPLAYNEXT 200
// Reserved room options (each is a byte)
#define ROOM_OPTIONS_RESERVED 4
#define LEGACY_TINT_IS_ENABLED 0x80000000
namespace AGS {
namespace Shared {
HRoomFileError OpenRoomFileFromAsset(const String &filename, RoomDataSource &src) {
// Cleanup source struct
src = RoomDataSource();
// Try to find and open room file
Stream *in = _GP(AssetMgr)->OpenAsset(filename);
if (in == nullptr)
return new RoomFileError(kRoomFileErr_FileOpenFailed, String::FromFormat("Filename: %s.", filename.GetCStr()));
src.Filename = filename;
src.InputStream.reset(in);
return ReadRoomHeader(src);
}
void ReadRoomObject(RoomObjectInfo &obj, Stream *in) {
obj.Sprite = (uint16_t)in->ReadInt16();
obj.X = in->ReadInt16();
obj.Y = in->ReadInt16();
obj.Room = in->ReadInt16();
obj.IsOn = in->ReadInt16() != 0;
}
void WriteRoomObject(const RoomObjectInfo &obj, Stream *out) {
// TODO: expand serialization into 32-bit values at least for the sprite index!!
out->WriteInt16((uint16_t)obj.Sprite);
out->WriteInt16((int16_t)obj.X);
out->WriteInt16((int16_t)obj.Y);
out->WriteInt16((int16_t)obj.Room);
out->WriteInt16(obj.IsOn ? 1 : 0);
}
// Main room data
HError ReadMainBlock(RoomStruct *room, Stream *in, RoomFileVersion data_ver) {
int bpp;
if (data_ver >= kRoomVersion_208)
bpp = in->ReadInt32();
else
bpp = 1;
if (bpp < 1)
bpp = 1;
room->BackgroundBPP = bpp;
room->WalkBehindCount = in->ReadInt16();
if (room->WalkBehindCount > MAX_WALK_BEHINDS)
return new RoomFileError(kRoomFileErr_IncompatibleEngine, String::FromFormat("Too many walk-behinds (in room: %d, max: %d).", room->WalkBehindCount, MAX_WALK_BEHINDS));
// Walk-behinds baselines
for (size_t i = 0; i < room->WalkBehindCount; ++i)
room->WalkBehinds[i].Baseline = in->ReadInt16();
room->HotspotCount = in->ReadInt32();
if (room->HotspotCount == 0)
room->HotspotCount = MIN_ROOM_HOTSPOTS;
if (room->HotspotCount > MAX_ROOM_HOTSPOTS)
return new RoomFileError(kRoomFileErr_IncompatibleEngine, String::FromFormat("Too many hotspots (in room: %d, max: %d).", room->HotspotCount, MAX_ROOM_HOTSPOTS));
// Hotspots walk-to points
for (size_t i = 0; i < room->HotspotCount; ++i) {
room->Hotspots[i].WalkTo.X = in->ReadInt16();
room->Hotspots[i].WalkTo.Y = in->ReadInt16();
}
// Hotspots names and script names
for (size_t i = 0; i < room->HotspotCount; ++i) {
if (data_ver >= kRoomVersion_3415)
room->Hotspots[i].Name = StrUtil::ReadString(in);
else if (data_ver >= kRoomVersion_303a)
room->Hotspots[i].Name = String::FromStream(in);
else
room->Hotspots[i].Name = String::FromStreamCount(in, LEGACY_HOTSPOT_NAME_LEN);
}
if (data_ver >= kRoomVersion_270) {
for (size_t i = 0; i < room->HotspotCount; ++i) {
if (data_ver >= kRoomVersion_3415)
room->Hotspots[i].ScriptName = StrUtil::ReadString(in);
else
room->Hotspots[i].ScriptName = String::FromStreamCount(in, LEGACY_MAX_SCRIPT_NAME_LEN);
}
}
// TODO: remove from format later
size_t polypoint_areas = in->ReadInt32();
if (polypoint_areas > 0)
return new RoomFileError(kRoomFileErr_IncompatibleEngine, "Legacy poly-point areas are no longer supported.");
/* NOTE: implementation hidden in room_file_deprecated.cpp
for (size_t i = 0; i < polypoint_areas; ++i)
wallpoints[i].Read(in);
*/
room->Edges.Top = in->ReadInt16();
room->Edges.Bottom = in->ReadInt16();
room->Edges.Left = in->ReadInt16();
room->Edges.Right = in->ReadInt16();
// Room objects
uint16_t obj_count = in->ReadInt16();
if (obj_count > MAX_ROOM_OBJECTS)
return new RoomFileError(kRoomFileErr_IncompatibleEngine, String::FromFormat("Too many objects (in room: %d, max: %d).", obj_count, MAX_ROOM_OBJECTS));
room->Objects.resize(obj_count);
for (auto &obj : room->Objects)
ReadRoomObject(obj, in);
// Legacy interactions
if (data_ver >= kRoomVersion_253) {
size_t localvar_count = in->ReadInt32();
if (localvar_count > 0) {
room->LocalVariables.resize(localvar_count);
for (size_t i = 0; i < localvar_count; ++i)
room->LocalVariables[i].Read(in);
}
}
if (data_ver >= kRoomVersion_241 && data_ver < kRoomVersion_300a) {
for (size_t i = 0; i < room->HotspotCount; ++i)
room->Hotspots[i].Interaction.reset(Interaction::CreateFromStream(in));
for (auto &obj : room->Objects)
obj.Interaction.reset(Interaction::CreateFromStream(in));
room->Interaction.reset(Interaction::CreateFromStream(in));
}
if (data_ver >= kRoomVersion_255b) {
room->RegionCount = in->ReadInt32();
if (room->RegionCount > MAX_ROOM_REGIONS)
return new RoomFileError(kRoomFileErr_IncompatibleEngine, String::FromFormat("Too many regions (in room: %d, max: %d).", room->RegionCount, MAX_ROOM_REGIONS));
if (data_ver < kRoomVersion_300a) {
for (size_t i = 0; i < room->RegionCount; ++i)
room->Regions[i].Interaction.reset(Interaction::CreateFromStream(in));
}
}
// Event script links
if (data_ver >= kRoomVersion_300a) {
room->EventHandlers.reset(InteractionScripts::CreateFromStream(in));
for (size_t i = 0; i < room->HotspotCount; ++i)
room->Hotspots[i].EventHandlers.reset(InteractionScripts::CreateFromStream(in));
for (auto &obj : room->Objects)
obj.EventHandlers.reset(InteractionScripts::CreateFromStream(in));
for (size_t i = 0; i < room->RegionCount; ++i)
room->Regions[i].EventHandlers.reset(InteractionScripts::CreateFromStream(in));
}
if (data_ver >= kRoomVersion_200_alpha) {
for (auto &obj : room->Objects)
obj.Baseline = in->ReadInt32();
room->Width = in->ReadInt16();
room->Height = in->ReadInt16();
}
if (data_ver >= kRoomVersion_262)
for (auto &obj : room->Objects)
obj.Flags = in->ReadInt16();
if (data_ver >= kRoomVersion_200_final)
room->MaskResolution = in->ReadInt16();
room->WalkAreaCount = MAX_WALK_AREAS;
if (data_ver >= kRoomVersion_240)
room->WalkAreaCount = in->ReadInt32();
if (room->WalkAreaCount > MAX_WALK_AREAS)
return new RoomFileError(kRoomFileErr_IncompatibleEngine, String::FromFormat("Too many walkable areas (in room: %d, max: %d).", room->WalkAreaCount, MAX_WALK_AREAS));
if (data_ver >= kRoomVersion_200_alpha7)
for (size_t i = 0; i < room->WalkAreaCount; ++i)
room->WalkAreas[i].ScalingFar = in->ReadInt16();
if (data_ver >= kRoomVersion_214)
for (size_t i = 0; i < room->WalkAreaCount; ++i)
room->WalkAreas[i].PlayerView = in->ReadInt16();
if (data_ver >= kRoomVersion_251) {
for (size_t i = 0; i < room->WalkAreaCount; ++i)
room->WalkAreas[i].ScalingNear = in->ReadInt16();
for (size_t i = 0; i < room->WalkAreaCount; ++i)
room->WalkAreas[i].Top = in->ReadInt16();
for (size_t i = 0; i < room->WalkAreaCount; ++i)
room->WalkAreas[i].Bottom = in->ReadInt16();
}
in->Seek(LEGACY_ROOM_PASSWORD_LENGTH); // skip password
room->Options.StartupMusic = in->ReadInt8();
room->Options.SaveLoadDisabled = in->ReadInt8() != 0;
room->Options.PlayerCharOff = in->ReadInt8() != 0;
room->Options.PlayerView = in->ReadInt8();
room->Options.MusicVolume = (RoomVolumeMod)in->ReadInt8();
room->Options.Flags = in->ReadInt8();
in->Seek(ROOM_OPTIONS_RESERVED);
room->MessageCount = in->ReadInt16();
if (room->MessageCount > MAX_MESSAGES)
return new RoomFileError(kRoomFileErr_IncompatibleEngine, String::FromFormat("Too many room messages (in room: %d, max: %d).", room->MessageCount, MAX_MESSAGES));
if (data_ver >= kRoomVersion_272)
room->GameID = in->ReadInt32();
if (data_ver >= kRoomVersion_pre114_3) {
for (size_t i = 0; i < room->MessageCount; ++i) {
room->MessageInfos[i].DisplayAs = in->ReadInt8();
room->MessageInfos[i].Flags = in->ReadInt8();
}
}
char buffer[3000];
for (size_t i = 0; i < room->MessageCount; ++i) {
if (data_ver >= kRoomVersion_261)
read_string_decrypt(in, buffer, sizeof(buffer));
else
StrUtil::ReadCStr(buffer, in, sizeof(buffer));
room->Messages[i] = buffer;
}
// Very old format legacy room animations (FullAnimation)
if (data_ver >= kRoomVersion_pre114_6) {
// TODO: remove from format later
size_t fullanim_count = in->ReadInt16();
if (fullanim_count > 0)
return new RoomFileError(kRoomFileErr_IncompatibleEngine, "Room animations are no longer supported.");
/* NOTE: implementation hidden in room_file_deprecated.cpp
in->ReadArray(&fullanims[0], sizeof(FullAnimation), fullanim_count);
*/
}
// Ancient "graphical scripts". We currently don't support them because
// there's no knowledge on how to convert them to modern engine.
if ((data_ver >= kRoomVersion_pre114_4) && (data_ver < kRoomVersion_250a)) {
return new RoomFileError(kRoomFileErr_IncompatibleEngine, "Pre-2.5 graphical scripts are no longer supported.");
/* NOTE: implementation hidden in room_file_deprecated.cpp
ReadPre250Scripts(in);
*/
}
if (data_ver >= kRoomVersion_114) {
// NOTE: this WA value was written for the second time here, for some weird reason
for (size_t i = 0; i < (size_t)MAX_WALK_AREAS; ++i)
room->WalkAreas[i].PlayerView = in->ReadInt16();
}
if (data_ver >= kRoomVersion_255b) {
for (size_t i = 0; i < room->RegionCount; ++i)
room->Regions[i].Light = in->ReadInt16();
for (size_t i = 0; i < room->RegionCount; ++i)
room->Regions[i].Tint = in->ReadInt32();
}
// Primary background (LZW or RLE compressed depending on format)
if (data_ver >= kRoomVersion_pre114_5)
room->BgFrames[0].Graphic.reset(
load_lzw(in, room->BackgroundBPP, &room->Palette));
else
room->BgFrames[0].Graphic.reset(load_rle_bitmap8(in));
// Area masks
if (data_ver >= kRoomVersion_255b)
room->RegionMask.reset(load_rle_bitmap8(in));
else if (data_ver >= kRoomVersion_114)
skip_rle_bitmap8(in); // an old version - clear the 'shadow' area into a blank regions bmp (???)
room->WalkAreaMask.reset(load_rle_bitmap8(in));
room->WalkBehindMask.reset(load_rle_bitmap8(in));
room->HotspotMask.reset(load_rle_bitmap8(in));
return HError::None();
}
// Room script sources (original text)
HError ReadScriptBlock(char *&buf, Stream *in, RoomFileVersion /*data_ver*/) {
size_t len = in->ReadInt32();
buf = new char[len + 1];
in->Read(buf, len);
buf[len] = 0;
for (size_t i = 0; i < len; ++i)
buf[i] += _G(passwencstring)[i % 11];
return HError::None();
}
// Compiled room script
HError ReadCompSc3Block(RoomStruct *room, Stream *in, RoomFileVersion /*data_ver*/) {
room->CompiledScript.reset(ccScript::CreateFromStream(in));
if (room->CompiledScript == nullptr)
return new RoomFileError(kRoomFileErr_ScriptLoadFailed, cc_get_error().ErrorString);
return HError::None();
}
// Room object names
HError ReadObjNamesBlock(RoomStruct *room, Stream *in, RoomFileVersion data_ver) {
size_t name_count = static_cast<uint8_t>(in->ReadInt8());
if (name_count != room->Objects.size())
return new RoomFileError(kRoomFileErr_InconsistentData,
String::FromFormat("In the object names block, expected name count: %zu, got %zu", room->Objects.size(), name_count));
for (auto &obj : room->Objects) {
if (data_ver >= kRoomVersion_3415)
obj.Name = StrUtil::ReadString(in);
else
obj.Name.ReadCount(in, LEGACY_MAXOBJNAMELEN);
}
return HError::None();
}
// Room object script names
HError ReadObjScNamesBlock(RoomStruct *room, Stream *in, RoomFileVersion data_ver) {
size_t name_count = static_cast<uint8_t>(in->ReadInt8());
if (name_count != room->Objects.size())
return new RoomFileError(kRoomFileErr_InconsistentData,
String::FromFormat("In the object script names block, expected name count: %zu, got %zu", room->Objects.size(), name_count));
for (auto &obj : room->Objects) {
if (data_ver >= kRoomVersion_3415)
obj.ScriptName = StrUtil::ReadString(in);
else
obj.ScriptName.ReadCount(in, LEGACY_MAX_SCRIPT_NAME_LEN);
}
return HError::None();
}
// Secondary backgrounds
HError ReadAnimBgBlock(RoomStruct *room, Stream *in, RoomFileVersion data_ver) {
room->BgFrameCount = in->ReadInt8();
if (room->BgFrameCount > MAX_ROOM_BGFRAMES)
return new RoomFileError(kRoomFileErr_IncompatibleEngine, String::FromFormat("Too many room backgrounds (in room: %d, max: %d).", room->BgFrameCount, MAX_ROOM_BGFRAMES));
room->BgAnimSpeed = in->ReadInt8();
if (data_ver >= kRoomVersion_255a) {
for (size_t i = 0; i < room->BgFrameCount; ++i)
room->BgFrames[i].IsPaletteShared = in->ReadInt8() != 0;
}
for (size_t i = 1; i < room->BgFrameCount; ++i) {
room->BgFrames[i].Graphic.reset(
load_lzw(in, room->BackgroundBPP, &room->BgFrames[i].Palette));
}
return HError::None();
}
// Read custom properties
HError ReadPropertiesBlock(RoomStruct *room, Stream *in, RoomFileVersion /*data_ver*/) {
int prop_ver = in->ReadInt32();
if (prop_ver != 1)
return new RoomFileError(kRoomFileErr_PropertiesBlockFormat, String::FromFormat("Expected version %d, got %d", 1, prop_ver));
int errors = 0;
errors += Properties::ReadValues(room->Properties, in);
for (size_t i = 0; i < room->HotspotCount; ++i)
errors += Properties::ReadValues(room->Hotspots[i].Properties, in);
for (auto &obj : room->Objects)
errors += Properties::ReadValues(obj.Properties, in);
if (errors > 0)
return new RoomFileError(kRoomFileErr_InvalidPropertyValues);
return HError::None();
}
HError ReadRoomBlock(RoomStruct *room, Stream *in, RoomFileBlock block, const String &ext_id,
soff_t block_len, RoomFileVersion data_ver) {
//
// First check classic block types, identified with a numeric id
//
switch (block) {
case kRoomFblk_Main:
return ReadMainBlock(room, in, data_ver);
case kRoomFblk_Script:
in->Seek(block_len); // no longer read source script text into RoomStruct
return HError::None();
case kRoomFblk_CompScript3:
return ReadCompSc3Block(room, in, data_ver);
case kRoomFblk_ObjectNames:
return ReadObjNamesBlock(room, in, data_ver);
case kRoomFblk_ObjectScNames:
return ReadObjScNamesBlock(room, in, data_ver);
case kRoomFblk_AnimBg:
return ReadAnimBgBlock(room, in, data_ver);
case kRoomFblk_Properties:
return ReadPropertiesBlock(room, in, data_ver);
case kRoomFblk_CompScript:
case kRoomFblk_CompScript2:
return new RoomFileError(kRoomFileErr_OldBlockNotSupported,
String::FromFormat("Type: %d.", block));
case kRoomFblk_None:
break; // continue to string ids
default:
return new RoomFileError(kRoomFileErr_UnknownBlockType,
String::FromFormat("Type: %d, known range: %d - %d.", block, kRoomFblk_Main, kRoomFblk_ObjectScNames));
}
// Add extensions here checking ext_id, which is an up to 16-chars name
if (ext_id.CompareNoCase("ext_sopts") == 0) {
StrUtil::ReadStringMap(room->StrOptions, in);
return HError::None();
}
return new RoomFileError(kRoomFileErr_UnknownBlockType,
String::FromFormat("Type: %s", ext_id.GetCStr()));
}
// RoomBlockReader reads whole room data, block by block
class RoomBlockReader : public DataExtReader {
public:
RoomBlockReader(RoomStruct *room, RoomFileVersion data_ver, Stream *in)
: DataExtReader(in,
kDataExt_NumID8 | ((data_ver < kRoomVersion_350) ? kDataExt_File32 : kDataExt_File64))
, _room(room)
, _dataVer(data_ver) {
}
// Helper function that extracts legacy room script
HError ReadRoomScript(String &script) {
HError err = FindOne(kRoomFblk_Script);
if (!err)
return err;
char *buf = nullptr;
err = ReadScriptBlock(buf, _in, _dataVer);
script = buf;
delete[] buf;
return err;
}
private:
String GetOldBlockName(int block_id) const override {
return GetRoomBlockName((RoomFileBlock)block_id);
}
HError ReadBlock(int block_id, const String &ext_id,
soff_t block_len, bool &read_next) override {
read_next = true;
return ReadRoomBlock(_room, _in, (RoomFileBlock)block_id, ext_id, block_len, _dataVer);
}
RoomStruct *_room{};
RoomFileVersion _dataVer{};
};
HRoomFileError ReadRoomData(RoomStruct *room, Stream *in, RoomFileVersion data_ver) {
room->DataVersion = data_ver;
RoomBlockReader reader(room, data_ver, in);
HError err = reader.Read();
return err ? HRoomFileError::None() :
new RoomFileError(kRoomFileErr_BlockListFailed, err);
}
HRoomFileError UpdateRoomData(RoomStruct *room, RoomFileVersion data_ver, bool game_is_hires, const std::vector<SpriteInfo> &sprinfos) {
if (data_ver < kRoomVersion_200_final)
room->MaskResolution = room->BgFrames[0].Graphic->GetWidth() > 320 ? kRoomHiRes : kRoomLoRes;
if (data_ver < kRoomVersion_3508) {
// Save legacy resolution if it DOES NOT match game's;
// otherwise it gets promoted to "real resolution"
if (room->MaskResolution == 1 && game_is_hires)
room->SetResolution(kRoomLoRes);
else if (room->MaskResolution > 1 && !game_is_hires)
room->SetResolution(kRoomHiRes);
}
// Old version - copy walkable areas to regions
if (data_ver < kRoomVersion_255b) {
if (!room->RegionMask)
room->RegionMask.reset(BitmapHelper::CreateBitmap(room->WalkAreaMask->GetWidth(), room->WalkAreaMask->GetHeight(), 8));
room->RegionMask->Blit(room->WalkAreaMask.get(), 0, 0, 0, 0, room->RegionMask->GetWidth(), room->RegionMask->GetHeight());
for (size_t i = 0; i < MAX_ROOM_REGIONS; ++i) {
// sic!! walkable areas were storing Light level in this field pre-2.55
room->Regions[i].Light = room->WalkAreas[i].PlayerView;
room->Regions[i].Tint = 255;
}
}
// Fill in dummy interaction objects into unused slots
// TODO: remove this later, need to rework the legacy interaction usage around the engine code to prevent crashes
if (data_ver < kRoomVersion_300a) {
if (!room->Interaction)
room->Interaction.reset(new Interaction());
for (size_t i = 0; i < (size_t)MAX_ROOM_HOTSPOTS; ++i)
if (!room->Hotspots[i].Interaction)
room->Hotspots[i].Interaction.reset(new Interaction());
for (auto &obj : room->Objects)
if (!obj.Interaction)
obj.Interaction.reset(new Interaction());
for (size_t i = 0; i < (size_t)MAX_ROOM_REGIONS; ++i)
if (!room->Regions[i].Interaction)
room->Regions[i].Interaction.reset(new Interaction());
}
// Upgade room object script names
if (data_ver < kRoomVersion_300a) {
for (auto &obj : room->Objects) {
if (obj.ScriptName.GetLength() > 0) {
String jibbledScriptName;
jibbledScriptName.Format("o%s", obj.ScriptName.GetCStr());
jibbledScriptName.MakeLower();
if (jibbledScriptName.GetLength() >= 2)
jibbledScriptName.SetAt(1, toupper(jibbledScriptName[1u]));
obj.ScriptName = jibbledScriptName;
}
}
}
// Pre-3.0.3, multiply up co-ordinates for high-res games to bring them
// to the proper game coordinate system.
// If you change this, also change convert_room_coordinates_to_data_res
// function in the engine
if (data_ver < kRoomVersion_303b && game_is_hires) {
const int mul = HIRES_COORD_MULTIPLIER;
for (auto &obj : room->Objects) {
obj.X *= mul;
obj.Y *= mul;
if (obj.Baseline > 0) {
obj.Baseline *= mul;
}
}
for (size_t i = 0; i < room->HotspotCount; ++i) {
room->Hotspots[i].WalkTo.X *= mul;
room->Hotspots[i].WalkTo.Y *= mul;
}
for (size_t i = 0; i < room->WalkBehindCount; ++i) {
room->WalkBehinds[i].Baseline *= mul;
}
room->Edges.Left *= mul;
room->Edges.Top *= mul;
room->Edges.Bottom *= mul;
room->Edges.Right *= mul;
room->Width *= mul;
room->Height *= mul;
}
// Adjust object Y coordinate by adding sprite's height
// NOTE: this is impossible to do without game sprite information loaded beforehand
// NOTE: this should be done after coordinate conversion above for simplicity
if (data_ver < kRoomVersion_300a) {
for (auto &obj : room->Objects)
obj.Y += sprinfos[obj.Sprite].Height;
}
if (data_ver >= kRoomVersion_251) {
// if they set a contiuously scaled area where the top
// and bottom zoom levels are identical, set it as a normal
// scaled area
for (size_t i = 0; i < room->WalkAreaCount; ++i) {
if (room->WalkAreas[i].ScalingFar == room->WalkAreas[i].ScalingNear)
room->WalkAreas[i].ScalingNear = NOT_VECTOR_SCALED;
}
}
// Convert the old format region tint saturation
if (data_ver < kRoomVersion_3404) {
for (size_t i = 0; i < room->RegionCount; ++i) {
if ((room->Regions[i].Tint & LEGACY_TINT_IS_ENABLED) != 0) {
room->Regions[i].Tint &= ~LEGACY_TINT_IS_ENABLED;
// older versions of the editor had a bug - work around it
int tint_amount = (room->Regions[i].Light > 0 ? room->Regions[i].Light : 50);
room->Regions[i].Tint |= (tint_amount & 0xFF) << 24;
room->Regions[i].Light = 255;
}
}
}
// Older format room messages had flags appended to the message string
// TODO: find out which data versions had these; is it safe to assume this was before kRoomVersion_pre114_3?
for (size_t i = 0; i < room->MessageCount; ++i) {
if (!room->Messages[i].IsEmpty() &&
static_cast<uint8_t>(room->Messages[i].GetLast()) == ROOM_MESSAGE_FLAG_DISPLAYNEXT) {
room->Messages[i].ClipRight(1);
room->MessageInfos[i].Flags |= MSG_DISPLAYNEXT;
}
}
// sync bpalettes[0] with room.pal
memcpy(room->BgFrames[0].Palette, room->Palette, sizeof(RGB) * 256);
return HRoomFileError::None();
}
HRoomFileError ExtractScriptText(String &script, Stream *in, RoomFileVersion data_ver) {
RoomBlockReader reader(nullptr, data_ver, in);
HError err = reader.ReadRoomScript(script);
if (!err)
new RoomFileError(kRoomFileErr_BlockListFailed, err);
return HRoomFileError::None();
}
void WriteInteractionScripts(const InteractionScripts *interactions, Stream *out) {
out->WriteInt32(interactions->ScriptFuncNames.size());
for (size_t i = 0; i < interactions->ScriptFuncNames.size(); ++i)
interactions->ScriptFuncNames[i].Write(out);
}
void WriteMainBlock(const RoomStruct *room, Stream *out) {
out->WriteInt32(room->BackgroundBPP);
out->WriteInt16((int16_t)room->WalkBehindCount);
for (size_t i = 0; i < room->WalkBehindCount; ++i)
out->WriteInt16(room->WalkBehinds[i].Baseline);
out->WriteInt32(room->HotspotCount);
for (size_t i = 0; i < room->HotspotCount; ++i) {
out->WriteInt16(room->Hotspots[i].WalkTo.X);
out->WriteInt16(room->Hotspots[i].WalkTo.Y);
}
for (size_t i = 0; i < room->HotspotCount; ++i)
Shared::StrUtil::WriteString(room->Hotspots[i].Name, out);
for (size_t i = 0; i < room->HotspotCount; ++i)
Shared::StrUtil::WriteString(room->Hotspots[i].ScriptName, out);
out->WriteInt32(0); // legacy poly-point areas
out->WriteInt16(room->Edges.Top);
out->WriteInt16(room->Edges.Bottom);
out->WriteInt16(room->Edges.Left);
out->WriteInt16(room->Edges.Right);
out->WriteInt16((int16_t)room->Objects.size());
for (const auto &obj : room->Objects) {
WriteRoomObject(obj, out);
}
out->WriteInt32(0); // legacy interaction vars
out->WriteInt32(MAX_ROOM_REGIONS);
WriteInteractionScripts(room->EventHandlers.get(), out);
for (size_t i = 0; i < room->HotspotCount; ++i)
WriteInteractionScripts(room->Hotspots[i].EventHandlers.get(), out);
for (const auto &obj : room->Objects)
WriteInteractionScripts(obj.EventHandlers.get(), out);
for (size_t i = 0; i < room->RegionCount; ++i)
WriteInteractionScripts(room->Regions[i].EventHandlers.get(), out);
for (const auto &obj : room->Objects)
out->WriteInt32(obj.Baseline);
out->WriteInt16(room->Width);
out->WriteInt16(room->Height);
for (const auto &obj : room->Objects)
out->WriteInt16(obj.Flags);
out->WriteInt16(room->MaskResolution);
out->WriteInt32(MAX_WALK_AREAS);
for (size_t i = 0; i < (size_t)MAX_WALK_AREAS; ++i)
out->WriteInt16(room->WalkAreas[i].ScalingFar);
for (size_t i = 0; i < (size_t)MAX_WALK_AREAS; ++i)
out->WriteInt16(room->WalkAreas[i].PlayerView);
for (size_t i = 0; i < (size_t)MAX_WALK_AREAS; ++i)
out->WriteInt16(room->WalkAreas[i].ScalingNear);
for (size_t i = 0; i < (size_t)MAX_WALK_AREAS; ++i)
out->WriteInt16(room->WalkAreas[i].Top);
for (size_t i = 0; i < (size_t)MAX_WALK_AREAS; ++i)
out->WriteInt16(room->WalkAreas[i].Bottom);
out->WriteByteCount(0, LEGACY_ROOM_PASSWORD_LENGTH);
out->WriteInt8(room->Options.StartupMusic);
out->WriteInt8(room->Options.SaveLoadDisabled ? 1 : 0);
out->WriteInt8(room->Options.PlayerCharOff ? 1 : 0);
out->WriteInt8(room->Options.PlayerView);
out->WriteInt8(room->Options.MusicVolume);
out->WriteInt8(room->Options.Flags);
out->WriteByteCount(0, ROOM_OPTIONS_RESERVED);
out->WriteInt16((int16_t)room->MessageCount);
out->WriteInt32(room->GameID);
for (size_t i = 0; i < room->MessageCount; ++i) {
out->WriteInt8(room->MessageInfos[i].DisplayAs);
out->WriteInt8(room->MessageInfos[i].Flags);
}
for (size_t i = 0; i < room->MessageCount; ++i)
write_string_encrypt(out, room->Messages[i].GetCStr());
out->WriteInt16(0); // legacy room animations
// NOTE: this WA value was written for the second time here, for some weird reason
for (size_t i = 0; i < (size_t)MAX_WALK_AREAS; ++i)
out->WriteInt16(room->WalkAreas[i].PlayerView);
for (size_t i = 0; i < (size_t)MAX_ROOM_REGIONS; ++i)
out->WriteInt16(room->Regions[i].Light);
for (size_t i = 0; i < (size_t)MAX_ROOM_REGIONS; ++i)
out->WriteInt32(room->Regions[i].Tint);
save_lzw(out, room->BgFrames[0].Graphic.get(), &room->Palette);
save_rle_bitmap8(out, room->RegionMask.get());
save_rle_bitmap8(out, room->WalkAreaMask.get());
save_rle_bitmap8(out, room->WalkBehindMask.get());
save_rle_bitmap8(out, room->HotspotMask.get());
}
void WriteCompSc3Block(const RoomStruct *room, Stream *out) {
room->CompiledScript->Write(out);
}
void WriteObjNamesBlock(const RoomStruct *room, Stream *out) {
out->WriteByte((uint8_t)room->Objects.size());
for (const auto &obj : room->Objects)
Shared::StrUtil::WriteString(obj.Name, out);
}
void WriteObjScNamesBlock(const RoomStruct *room, Stream *out) {
out->WriteByte((uint8_t)room->Objects.size());
for (const auto &obj : room->Objects)
Shared::StrUtil::WriteString(obj.ScriptName, out);
}
void WriteAnimBgBlock(const RoomStruct *room, Stream *out) {
out->WriteByte((int8_t)room->BgFrameCount);
out->WriteByte(room->BgAnimSpeed);
for (size_t i = 0; i < room->BgFrameCount; ++i)
out->WriteInt8(room->BgFrames[i].IsPaletteShared ? 1 : 0);
for (size_t i = 1; i < room->BgFrameCount; ++i)
save_lzw(out, room->BgFrames[i].Graphic.get(), &room->BgFrames[i].Palette);
}
void WritePropertiesBlock(const RoomStruct *room, Stream *out) {
out->WriteInt32(1); // Version 1 of properties block
Properties::WriteValues(room->Properties, out);
for (size_t i = 0; i < room->HotspotCount; ++i)
Properties::WriteValues(room->Hotspots[i].Properties, out);
for (const auto &obj : room->Objects)
Properties::WriteValues(obj.Properties, out);
}
void WriteStrOptions(const RoomStruct *room, Stream *out) {
StrUtil::WriteStringMap(room->StrOptions, out);
}
HRoomFileError WriteRoomData(const RoomStruct *room, Stream *out, RoomFileVersion data_ver) {
if (data_ver < kRoomVersion_Current)
return new RoomFileError(kRoomFileErr_FormatNotSupported, "We no longer support saving room in the older format.");
// Header
out->WriteInt16(data_ver);
// Main data
WriteRoomBlock(room, kRoomFblk_Main, WriteMainBlock, out);
// Compiled script
if (room->CompiledScript)
WriteRoomBlock(room, kRoomFblk_CompScript3, WriteCompSc3Block, out);
// Object names
if (room->Objects.size() > 0) {
WriteRoomBlock(room, kRoomFblk_ObjectNames, WriteObjNamesBlock, out);
WriteRoomBlock(room, kRoomFblk_ObjectScNames, WriteObjScNamesBlock, out);
}
// Secondary background frames
if (room->BgFrameCount > 1)
WriteRoomBlock(room, kRoomFblk_AnimBg, WriteAnimBgBlock, out);
// Custom properties
WriteRoomBlock(room, kRoomFblk_Properties, WritePropertiesBlock, out);
// String options
WriteRoomBlock(room, "ext_sopts", WriteStrOptions, out);
// Write end of room file
out->WriteByte(kRoomFile_EOF);
return HRoomFileError::None();
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,141 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// This unit provides functions for reading compiled room file (CRM)
// into the RoomStruct structure, as well as extracting separate components,
// such as room scripts.
//
//=============================================================================
#ifndef AGS_SHARED_GAME_ROOM_FILE_H
#define AGS_SHARED_GAME_ROOM_FILE_H
#include "common/std/memory.h"
#include "common/std/vector.h"
#include "ags/shared/core/platform.h"
#include "ags/shared/game/room_version.h"
#include "ags/shared/util/error.h"
#include "ags/shared/util/stream.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
struct SpriteInfo;
namespace AGS {
namespace Shared {
class RoomStruct;
enum RoomFileErrorType {
kRoomFileErr_NoError,
kRoomFileErr_FileOpenFailed,
kRoomFileErr_FormatNotSupported,
kRoomFileErr_BlockListFailed,
kRoomFileErr_UnknownBlockType,
kRoomFileErr_OldBlockNotSupported,
kRoomFileErr_BlockDataOverlapping,
kRoomFileErr_IncompatibleEngine,
kRoomFileErr_ScriptLoadFailed,
kRoomFileErr_InconsistentData,
kRoomFileErr_PropertiesBlockFormat,
kRoomFileErr_InvalidPropertyValues,
kRoomFileErr_BlockNotFound
};
enum RoomFileBlock {
kRoomFblk_None = 0,
// Main room data
kRoomFblk_Main = 1,
// Room script text source (was present in older room formats)
kRoomFblk_Script = 2,
// Old versions of compiled script (no longer supported)
kRoomFblk_CompScript = 3,
kRoomFblk_CompScript2 = 4,
// Names of the room objects
kRoomFblk_ObjectNames = 5,
// Secondary room backgrounds
kRoomFblk_AnimBg = 6,
// Contemporary compiled script
kRoomFblk_CompScript3 = 7,
// Custom properties
kRoomFblk_Properties = 8,
// Script names of the room objects
kRoomFblk_ObjectScNames = 9,
// End of room data tag
kRoomFile_EOF = 0xFF
};
String GetRoomFileErrorText(RoomFileErrorType err);
String GetRoomBlockName(RoomFileBlock id);
typedef TypedCodeError<RoomFileErrorType, GetRoomFileErrorText> RoomFileError;
typedef ErrorHandle<RoomFileError> HRoomFileError;
#ifdef AGS_PLATFORM_SCUMMVM
typedef std::shared_ptr<Stream> UStream;
#else
typedef std::unique_ptr<Stream> UStream;
#endif
// RoomDataSource defines a successfully opened room file
struct RoomDataSource {
// Name of the asset file
String Filename;
// Room file format version
RoomFileVersion DataVersion;
// A ponter to the opened stream
UStream InputStream;
RoomDataSource();
};
// Opens room data for reading from an arbitrary file
HRoomFileError OpenRoomFile(const String &filename, RoomDataSource &src);
// Opens room data for reading from asset of a given name
HRoomFileError OpenRoomFileFromAsset(const String &filename, RoomDataSource &src);
// Reads room data
HRoomFileError ReadRoomData(RoomStruct *room, Stream *in, RoomFileVersion data_ver);
// Applies necessary updates, conversions and fixups to the loaded data
// making it compatible with current engine
HRoomFileError UpdateRoomData(RoomStruct *room, RoomFileVersion data_ver, bool game_is_hires, const std::vector<SpriteInfo> &sprinfos);
// Extracts text script from the room file, if it's available.
// Historically, text sources were kept inside packed room files before AGS 3.*.
HRoomFileError ExtractScriptText(String &script, Stream *in, RoomFileVersion data_ver);
// Writes all room data to the stream
HRoomFileError WriteRoomData(const RoomStruct *room, Stream *out, RoomFileVersion data_ver);
// Reads room data header using stream assigned to RoomDataSource;
// tests and saves its format index if successful
HRoomFileError ReadRoomHeader(RoomDataSource &src);
typedef void(*PfnWriteRoomBlock)(const RoomStruct *room, Stream *out);
// Writes room block with a new-style string id
void WriteRoomBlock(const RoomStruct *room, const String &ext_id, PfnWriteRoomBlock writer, Stream *out);
// Writes room block with a old-style numeric id
void WriteRoomBlock(const RoomStruct *room, RoomFileBlock block, PfnWriteRoomBlock writer, Stream *out);
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,131 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/game/room_file.h"
#include "ags/shared/util/data_ext.h"
#include "ags/shared/util/file.h"
#include "ags/shared/debugging/out.h"
#include "ags/globals.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
RoomDataSource::RoomDataSource()
: DataVersion(kRoomVersion_Undefined) {
}
String GetRoomFileErrorText(RoomFileErrorType err) {
switch (err) {
case kRoomFileErr_NoError:
return "No error.";
case kRoomFileErr_FileOpenFailed:
return "Room file was not found or could not be opened.";
case kRoomFileErr_FormatNotSupported:
return "Format version not supported.";
case kRoomFileErr_BlockListFailed:
return "There was an error reading room data..";
case kRoomFileErr_UnknownBlockType:
return "Unknown block type.";
case kRoomFileErr_OldBlockNotSupported:
return "Block type is too old and not supported by this version of the engine.";
case kRoomFileErr_BlockDataOverlapping:
return "Block data overlapping.";
case kRoomFileErr_IncompatibleEngine:
return "This engine cannot handle requested room content.";
case kRoomFileErr_ScriptLoadFailed:
return "Script load failed.";
case kRoomFileErr_InconsistentData:
return "Inconsistent room data, or file is corrupted.";
case kRoomFileErr_PropertiesBlockFormat:
return "Unknown format of the custom properties block.";
case kRoomFileErr_InvalidPropertyValues:
return "Errors encountered when reading custom properties.";
case kRoomFileErr_BlockNotFound:
return "Required block was not found.";
default:
return "Unknown error.";
}
}
HRoomFileError OpenRoomFile(const String &filename, RoomDataSource &src) {
// Cleanup source struct
src = RoomDataSource();
// Try to open room file
Stream *in = File::OpenFileRead(filename);
if (in == nullptr)
return new RoomFileError(kRoomFileErr_FileOpenFailed, String::FromFormat("Filename: %s.", filename.GetCStr()));
src.Filename = filename;
src.InputStream.reset(in);
return ReadRoomHeader(src);
}
// Read room data header and check that we support this format
HRoomFileError ReadRoomHeader(RoomDataSource &src) {
src.DataVersion = (RoomFileVersion)src.InputStream->ReadInt16();
if (src.DataVersion < kRoomVersion_250b || src.DataVersion > kRoomVersion_Current)
return new RoomFileError(kRoomFileErr_FormatNotSupported, String::FromFormat("Required format version: %d, supported %d - %d", src.DataVersion, kRoomVersion_250b, kRoomVersion_Current));
return HRoomFileError::None();
}
String GetRoomBlockName(RoomFileBlock id) {
switch (id) {
case kRoomFblk_None: return "None";
case kRoomFblk_Main: return "Main";
case kRoomFblk_Script: return "TextScript";
case kRoomFblk_CompScript: return "CompScript";
case kRoomFblk_CompScript2: return "CompScript2";
case kRoomFblk_ObjectNames: return "ObjNames";
case kRoomFblk_AnimBg: return "AnimBg";
case kRoomFblk_CompScript3: return "CompScript3";
case kRoomFblk_Properties: return "Properties";
case kRoomFblk_ObjectScNames: return "ObjScNames";
case kRoomFile_EOF: return "EOF";
default: return "unknown";
}
}
static PfnWriteRoomBlock writer_writer;
static const RoomStruct *writer_room;
static void WriteRoomBlockWriter(Stream *out) {
writer_writer(writer_room, out);
}
// Helper for new-style blocks with string id
void WriteRoomBlock(const RoomStruct *room, const String &ext_id, PfnWriteRoomBlock writer, Stream *out) {
writer_writer = writer;
writer_room = room;
WriteExtBlock(ext_id, WriteRoomBlockWriter,
kDataExt_NumID8 | kDataExt_File64, out);
}
// Helper for old-style blocks with only numeric id
void WriteRoomBlock(const RoomStruct *room, RoomFileBlock block, PfnWriteRoomBlock writer, Stream *out) {
writer_writer = writer;
writer_room = room;
WriteExtBlock(block, WriteRoomBlockWriter,
kDataExt_NumID8 | kDataExt_File64, out);
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,142 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// Deprecated room stuff. Removed from room class and load routine because this
// data is no longer supported in the engine. Perhaps move to some legacy
// knowledge base; or restore when it's possible to support ancient games.
//
//=============================================================================
#if defined (OBSOLETE)
#include "ags/shared/ac/common.h"
#include "ags/shared/util/stream.h"
using namespace AGS::Shared;
#define AE_WAITFLAG 0x80000000
#define MAXANIMSTAGES 10
struct AnimationStruct {
int x, y;
int data;
int object;
int speed;
int8 action;
int8 wait;
AnimationStruct() {
action = 0;
object = 0;
wait = 1;
speed = 5;
}
};
struct FullAnimation {
AnimationStruct stage[MAXANIMSTAGES];
int numstages;
FullAnimation() {
numstages = 0;
}
};
#define MAXPOINTS 30
struct PolyPoints {
int x[MAXPOINTS];
int y[MAXPOINTS];
int numpoints;
void add_point(int x, int y);
PolyPoints() {
numpoints = 0;
}
void Read(AGS::Shared::Stream *in);
};
#define MAXANIMS 10
// Just a list of cut out data
struct DeprecatedRoomStruct {
// Full-room animations
int16_t numanims;
FullAnimation anims[MAXANIMS];
// Polygonal walkable areas (unknown version)
int32_t numwalkareas;
PolyPoints wallpoints[MAX_WALK_AREAS];
// Unknown flags
int16_t flagstates[MAX_LEGACY_ROOM_FLAGS];
};
void PolyPoints::add_point(int x, int y) {
x[numpoints] = x;
y[numpoints] = y;
numpoints++;
if (numpoints >= MAXPOINTS)
quit("too many poly points added");
}
void PolyPoints::Read(Stream *in) {
in->ReadArrayOfInt32(x, MAXPOINTS);
in->ReadArrayOfInt32(y, MAXPOINTS);
numpoints = in->ReadInt32();
}
//
// Pre-2.5 scripts (we don't know how to convert them for the modern engine)
//
#define SCRIPT_CONFIG_VERSION 1
HRoomFileError ReadAncientScriptConfig(Stream *in) {
int fmt = in->ReadInt32();
if (fmt != SCRIPT_CONFIG_VERSION)
return new RoomFileError(kRoomFileErr_FormatNotSupported, String::FromFormat("Invalid script configuration format (in room: %d, expected: %d).", fmt, SCRIPT_CONFIG_VERSION));
size_t var_count = in->ReadInt32();
for (size_t i = 0; i < var_count; ++i) {
size_t len = in->ReadInt8();
in->Seek(len);
}
return HRoomFileError::None();
}
HRoomFileError ReadAncientGraphicalScripts(Stream *in) {
do {
int ct = in->ReadInt32();
if (ct == -1 || in->EOS())
break;
size_t len = in->ReadInt32();
in->Seek(len);
} while (true);
return HRoomFileError::None();
}
HRoomFileError ReadPre250Scripts(Stream *in) {
HRoomFileError err = ReadAncientScriptConfig(in);
if (err)
err = ReadAncientGraphicalScripts(in);
return err;
}
#endif // OBSOLETE

View File

@@ -0,0 +1,286 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/ac/common.h" // quit
#include "ags/shared/game/room_file.h"
#include "ags/shared/game/room_struct.h"
#include "ags/shared/gfx/bitmap.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
RoomOptions::RoomOptions()
: StartupMusic(0)
, SaveLoadDisabled(false)
, PlayerCharOff(false)
, PlayerView(0)
, MusicVolume(kRoomVolumeNormal)
, Flags(0) {
}
RoomBgFrame::RoomBgFrame()
: IsPaletteShared(false) {
memset(Palette, 0, sizeof(Palette));
}
RoomEdges::RoomEdges()
: Left(0)
, Right(0)
, Top(0)
, Bottom(0) {
}
RoomEdges::RoomEdges(int l, int r, int t, int b)
: Left(l)
, Right(r)
, Top(t)
, Bottom(b) {
}
RoomObjectInfo::RoomObjectInfo()
: Sprite(0)
, X(0)
, Y(0)
, Room(-1)
, IsOn(false)
, Baseline(0xFF)
, Flags(0) {
}
RoomRegion::RoomRegion()
: Light(0)
, Tint(0) {
}
WalkArea::WalkArea()
: CharacterView(0)
, ScalingFar(0)
, ScalingNear(NOT_VECTOR_SCALED)
, PlayerView(0)
, Top(-1)
, Bottom(-1) {
}
WalkBehind::WalkBehind()
: Baseline(0) {
}
MessageInfo::MessageInfo()
: DisplayAs(0)
, Flags(0) {
}
RoomStruct::RoomStruct() {
InitDefaults();
}
RoomStruct::~RoomStruct() {
Free();
}
void RoomStruct::Free() {
for (size_t i = 0; i < (size_t)MAX_ROOM_BGFRAMES; ++i)
BgFrames[i].Graphic.reset();
HotspotMask.reset();
RegionMask.reset();
WalkAreaMask.reset();
WalkBehindMask.reset();
LocalVariables.clear();
Interaction.reset();
Properties.clear();
for (size_t i = 0; i < (size_t)MAX_ROOM_HOTSPOTS; ++i) {
Hotspots[i].Interaction.reset();
Hotspots[i].Properties.clear();
}
Objects.clear();
for (size_t i = 0; i < (size_t)MAX_ROOM_REGIONS; ++i) {
Regions[i].Interaction.reset();
Regions[i].Properties.clear();
}
FreeMessages();
FreeScripts();
}
void RoomStruct::FreeMessages() {
for (size_t i = 0; i < MessageCount; ++i) {
Messages[i].Free();
MessageInfos[i] = MessageInfo();
}
MessageCount = 0;
}
void RoomStruct::FreeScripts() {
CompiledScript.reset();
EventHandlers.reset();
for (size_t i = 0; i < HotspotCount; ++i)
Hotspots[i].EventHandlers.reset();
for (auto &obj : Objects)
obj.EventHandlers.reset();
for (size_t i = 0; i < RegionCount; ++i)
Regions[i].EventHandlers.reset();
}
void RoomStruct::InitDefaults() {
DataVersion = kRoomVersion_Current;
GameID = NO_GAME_ID_IN_ROOM_FILE;
_resolution = kRoomRealRes;
MaskResolution = 1;
Width = 320;
Height = 200;
Options = RoomOptions();
Edges = RoomEdges(0, 317, 40, 199);
BgFrameCount = 1;
HotspotCount = 0;
RegionCount = 0;
WalkAreaCount = 0;
WalkBehindCount = 0;
MessageCount = 0;
for (size_t i = 0; i < (size_t)MAX_ROOM_HOTSPOTS; ++i)
Hotspots[i] = RoomHotspot();
for (size_t i = 0; i < (size_t)MAX_ROOM_REGIONS; ++i)
Regions[i] = RoomRegion();
for (size_t i = 0; i < (size_t)MAX_WALK_AREAS; ++i)
WalkAreas[i] = WalkArea();
for (size_t i = 0; i < (size_t)MAX_WALK_BEHINDS; ++i)
WalkBehinds[i] = WalkBehind();
BackgroundBPP = 1;
BgAnimSpeed = 5;
memset(Palette, 0, sizeof(Palette));
}
void RoomStruct::SetResolution(RoomResolutionType type) {
_resolution = type;
}
Bitmap *RoomStruct::GetMask(RoomAreaMask mask) const {
switch (mask) {
case kRoomAreaHotspot: return HotspotMask.get();
case kRoomAreaWalkBehind: return WalkBehindMask.get();
case kRoomAreaWalkable: return WalkAreaMask.get();
case kRoomAreaRegion: return RegionMask.get();
default: return nullptr;
}
}
float RoomStruct::GetMaskScale(RoomAreaMask mask) const {
switch (mask) {
case kRoomAreaWalkBehind: return 1.f; // walk-behinds always 1:1 with room size
case kRoomAreaHotspot:
case kRoomAreaWalkable:
case kRoomAreaRegion:
return 1.f / MaskResolution;
default:
return 0.f;
}
}
bool RoomStruct::HasRegionLightLevel(int id) const {
if (id >= 0 && id < MAX_ROOM_REGIONS)
return Regions[id].Tint == 0;
return false;
}
bool RoomStruct::HasRegionTint(int id) const {
if (id >= 0 && id < MAX_ROOM_REGIONS)
return Regions[id].Tint != 0;
return false;
}
int RoomStruct::GetRegionLightLevel(int id) const {
if (id >= 0 && id < MAX_ROOM_REGIONS)
return HasRegionLightLevel(id) ? Regions[id].Light : 0;
return 0;
}
int RoomStruct::GetRegionTintLuminance(int id) const {
if (id >= 0 && id < MAX_ROOM_REGIONS)
return HasRegionTint(id) ? (Regions[id].Light * 10) / 25 : 0;
return 0;
}
void load_room(const String &filename, RoomStruct *room, bool game_is_hires, const std::vector<SpriteInfo> &sprinfos) {
room->Free();
room->InitDefaults();
RoomDataSource src;
HRoomFileError err = OpenRoomFileFromAsset(filename, src);
if (err) {
err = ReadRoomData(room, src.InputStream.get(), src.DataVersion);
if (err)
err = UpdateRoomData(room, src.DataVersion, game_is_hires, sprinfos);
}
if (!err)
quitprintf("Unable to load the room file '%s'.\n%s.", filename.GetCStr(), err->FullMessage().GetCStr());
}
PBitmap FixBitmap(PBitmap bmp, int width, int height) {
Bitmap *new_bmp = BitmapHelper::AdjustBitmapSize(bmp.get(), width, height);
if (new_bmp != bmp.get())
return PBitmap(new_bmp);
return bmp;
}
void UpscaleRoomBackground(RoomStruct *room, bool game_is_hires) {
if (room->DataVersion >= kRoomVersion_303b || !game_is_hires)
return;
for (size_t i = 0; i < room->BgFrameCount; ++i)
room->BgFrames[i].Graphic = FixBitmap(room->BgFrames[i].Graphic, room->Width, room->Height);
FixRoomMasks(room);
}
void FixRoomMasks(RoomStruct *room) {
if (room->MaskResolution <= 0)
return;
Bitmap *bkg = room->BgFrames[0].Graphic.get();
if (bkg == nullptr)
return;
// TODO: this issue is somewhat complicated. Original code was relying on
// room->Width and Height properties. But in the engine these are saved
// already converted to data resolution which may be "low-res". Since this
// function is shared between engine and editor we do not know if we need
// to upscale them.
// For now room width/height is always equal to background bitmap.
int base_width = bkg->GetWidth();
int base_height = bkg->GetHeight();
int low_width = base_width / room->MaskResolution;
int low_height = base_height / room->MaskResolution;
// Walk-behinds are always 1:1 of the primary background.
// Other masks are 1:x where X is MaskResolution.
room->WalkBehindMask = FixBitmap(room->WalkBehindMask, base_width, base_height);
room->HotspotMask = FixBitmap(room->HotspotMask, low_width, low_height);
room->RegionMask = FixBitmap(room->RegionMask, low_width, low_height);
room->WalkAreaMask = FixBitmap(room->WalkAreaMask, low_width, low_height);
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//
// RoomStruct, a class describing initial room data.
//
// Because of the imperfect implementation there is inconsistency in how
// this data is interpreted at the runtime.
// Some of that data is never supposed to be changed at runtime. Another
// may be changed, but these changes are lost as soon as room is unloaded.
// The changes that must remain in memory are kept as separate classes:
// see RoomStatus, RoomObject etc.
//
// Partially this is because same class was used for both engine and editor,
// while runtime code was not available for the editor.
//
// This is also the reason why some classes here are named with the "Info"
// postfix. For example, RoomObjectInfo is the initial object data, and
// there is also RoomObject runtime-only class for mutable data.
//
// [ivan-mogilko] In my opinion, eventually there should be only one room class
// and one class per room entity, regardless of whether code is shared with
// the editor or not. But that would require extensive refactor/rewrite of
// the engine code, and savegame read/write code.
//
//=============================================================================
#ifndef AGS_SHARED_GAME_ROOM_INFO_H
#define AGS_SHARED_GAME_ROOM_INFO_H
#include "common/std/memory.h"
#include "ags/lib/allegro.h" // RGB
#include "ags/shared/ac/common_defines.h"
#include "ags/shared/game/interactions.h"
#include "ags/shared/util/geometry.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
struct ccScript;
struct SpriteInfo;
typedef std::shared_ptr<ccScript> PScript;
// TODO: move the following enums under AGS::Shared namespace
// later, when more engine source is put in AGS namespace and
// refactored.
// Room's area mask type
enum RoomAreaMask {
kRoomAreaNone = 0,
kRoomAreaHotspot,
kRoomAreaWalkBehind,
kRoomAreaWalkable,
kRoomAreaRegion
};
// Room's audio volume modifier
enum RoomVolumeMod {
kRoomVolumeQuietest = -3,
kRoomVolumeQuieter = -2,
kRoomVolumeQuiet = -1,
kRoomVolumeNormal = 0,
kRoomVolumeLoud = 1,
kRoomVolumeLouder = 2,
kRoomVolumeLoudest = 3,
// These two options are only settable at runtime by SetMusicVolume()
kRoomVolumeExtra1 = 4,
kRoomVolumeExtra2 = 5,
kRoomVolumeMin = kRoomVolumeQuietest,
kRoomVolumeMax = kRoomVolumeExtra2,
};
// Extended room boolean options
enum RoomFlags {
kRoomFlag_BkgFrameLocked = 0x01
};
// Flag tells that walkable area does not have continious zoom
#define NOT_VECTOR_SCALED -10000
// Flags tells that room is not linked to particular game ID
#define NO_GAME_ID_IN_ROOM_FILE 16325
#define MAX_ROOM_BGFRAMES 5 // max number of frames in animating bg scene
#define MAX_ROOM_HOTSPOTS 50 // v2.62: 20 -> 30; v2.8: -> 50
#define MAX_ROOM_OBJECTS_v300 40 // for some legacy logic support
#define MAX_ROOM_OBJECTS 256 // v3.6.0: 40 -> 256 (now limited by room format)
#define MAX_ROOM_REGIONS 16
#define MAX_WALK_AREAS 16
#define MAX_WALK_BEHINDS 16
#define MAX_MESSAGES 100
namespace AGS {
namespace Shared {
class Bitmap;
class Stream;
typedef std::shared_ptr<Bitmap> PBitmap;
// Various room options
struct RoomOptions {
// Index of the startup music in the room
// this is a deprecated option, used before 3.2.* with old audio API.
int StartupMusic;
// If saving and loading game is disabled in the room;
// this is a deprecated option that affects only built-in save/load dialogs
bool SaveLoadDisabled;
// If player character is turned off in the room
bool PlayerCharOff;
// Apply player character's normal view when entering this room
int PlayerView;
// Room's music volume modifier
RoomVolumeMod MusicVolume;
// A collection of RoomFlags
int Flags;
RoomOptions();
};
// Single room background frame
struct RoomBgFrame {
PBitmap Graphic;
// Palette is only valid in 8-bit games
RGB Palette[256];
// Tells if this frame should keep previous frame palette instead of using its own
bool IsPaletteShared;
RoomBgFrame();
};
// Describes room edges (coordinates of four edges)
struct RoomEdges {
int32_t Left;
int32_t Right;
int32_t Top;
int32_t Bottom;
RoomEdges();
RoomEdges(int l, int r, int t, int b);
};
// Room hotspot description
struct RoomHotspot {
String Name;
String ScriptName;
// Custom properties
StringIMap Properties;
// Old-style interactions
PInteraction Interaction;
// Event script links
PInteractionScripts EventHandlers;
// Player will automatically walk here when interacting with hotspot
Point WalkTo;
};
// Room object description
struct RoomObjectInfo {
int32_t Room;
int32_t X;
int32_t Y;
int32_t Sprite;
bool IsOn;
// Object's z-order in the room, or -1 (use Y)
int32_t Baseline;
int32_t Flags;
String Name;
String ScriptName;
// Custom properties
StringIMap Properties;
// Old-style interactions
PInteraction Interaction;
// Event script links
PInteractionScripts EventHandlers;
RoomObjectInfo();
};
// Room region description
struct RoomRegion {
// Light level (-100 -> +100) or Tint luminance (0 - 255)
int32_t Light;
// Tint setting (R-B-G-S)
int32_t Tint;
// Custom properties
StringIMap Properties;
// Old-style interactions
PInteraction Interaction;
// Event script links
PInteractionScripts EventHandlers;
RoomRegion();
};
// Walkable area description
struct WalkArea {
// Apply player character's normal view on this area
int32_t CharacterView;
// Character's scaling (-100 -> +100 %)
// General scaling, or scaling at the farthest point
int32_t ScalingFar;
// Scaling at the nearest point, or NOT_VECTOR_SCALED for uniform scaling
int32_t ScalingNear;
// Optional override for player character view
int32_t PlayerView;
// Top and bottom Y of the area
int32_t Top;
int32_t Bottom;
WalkArea();
};
// Walk-behind description
struct WalkBehind {
// Object's z-order in the room
int32_t Baseline;
WalkBehind();
};
// Room messages
#define MSG_DISPLAYNEXT 0x01 // supercedes using alt-200 at end of message
#define MSG_TIMELIMIT 0x02
struct MessageInfo {
int8 DisplayAs; // 0 - std display window, >=1 - as character's speech
int8 Flags; // combination of MSG_xxx flags
MessageInfo();
};
// Room's legacy resolution type
enum RoomResolutionType {
kRoomRealRes = 0, // room should always be treated as-is
kRoomLoRes = 1, // created for low-resolution game
kRoomHiRes = 2 // created for high-resolution game
};
//
// Description of a single room.
// This class contains initial room data. Some of it may still be modified
// at the runtime, but then these changes get lost as soon as room is unloaded.
//
class RoomStruct {
public:
RoomStruct();
~RoomStruct();
// Gets if room should adjust its base size depending on game's resolution
inline bool IsRelativeRes() const {
return _resolution != kRoomRealRes;
}
// Gets if room belongs to high resolution
inline bool IsLegacyHiRes() const {
return _resolution == kRoomHiRes;
}
// Gets legacy resolution type
inline RoomResolutionType GetResolutionType() const {
return _resolution;
}
// Releases room resources
void Free();
// Release room messages and scripts correspondingly. These two functions are needed
// at very specific occasion when only part of the room resources has to be freed.
void FreeMessages();
void FreeScripts();
// Init default room state
void InitDefaults();
// Set legacy resolution type
void SetResolution(RoomResolutionType type);
// Gets bitmap of particular mask layer
Bitmap *GetMask(RoomAreaMask mask) const;
// Gets mask's scale relative to the room's background size
float GetMaskScale(RoomAreaMask mask) const;
// TODO: see later whether it may be more convenient to move these to the Region class instead.
// Gets if the given region has light level set
bool HasRegionLightLevel(int id) const;
// Gets if the given region has a tint set
bool HasRegionTint(int id) const;
// Gets region's light level in -100 to 100 range value; returns 0 (default level) if region's tint is set
int GetRegionLightLevel(int id) const;
// Gets region's tint luminance in 0 to 100 range value; returns 0 if region's light level is set
int GetRegionTintLuminance(int id) const;
// TODO: all members are currently public because they are used everywhere; hide them later
public:
// Game's unique ID, corresponds to GameSetupStructBase::uniqueid.
// If this field has a valid value and does not match actual game's id,
// then engine will refuse to start this room.
// May be set to NO_GAME_ID_IN_ROOM_FILE to let it run within any game.
int32_t GameID;
// Loaded room file's data version. This value may be used to know when
// the room must have behavior specific to certain version of AGS.
int32_t DataVersion;
// Room region masks resolution. Defines the relation between room and mask units.
// Mask point is calculated as roompt / MaskResolution. Must be >= 1.
int32_t MaskResolution;
// Size of the room, in logical coordinates (= pixels)
int32_t Width;
int32_t Height;
// Primary room palette (8-bit games)
RGB Palette[256];
// Basic room options
RoomOptions Options;
// Background frames
int32_t BackgroundBPP; // bytes per pixel
size_t BgFrameCount;
RoomBgFrame BgFrames[MAX_ROOM_BGFRAMES];
// Speed at which background frames are changing, 0 - no auto animation
int32_t BgAnimSpeed;
// Edges
RoomEdges Edges;
// Region masks
PBitmap HotspotMask;
PBitmap RegionMask;
PBitmap WalkAreaMask;
PBitmap WalkBehindMask;
// Room entities
size_t HotspotCount;
RoomHotspot Hotspots[MAX_ROOM_HOTSPOTS];
std::vector<RoomObjectInfo> Objects;
size_t RegionCount;
RoomRegion Regions[MAX_ROOM_REGIONS];
size_t WalkAreaCount;
WalkArea WalkAreas[MAX_WALK_AREAS];
size_t WalkBehindCount;
WalkBehind WalkBehinds[MAX_WALK_BEHINDS];
// Old numbered room messages (used with DisplayMessage, etc)
size_t MessageCount;
String Messages[MAX_MESSAGES];
MessageInfo MessageInfos[MAX_MESSAGES];
// Custom properties
StringIMap Properties;
// Old-style interactions
InterVarVector LocalVariables;
PInteraction Interaction;
// Event script links
PInteractionScripts EventHandlers;
// Compiled room script
PScript CompiledScript;
// Various extended options with string values, meta-data etc
StringMap StrOptions;
private:
// Room's legacy resolution type, defines relation room and game's resolution
RoomResolutionType _resolution;
};
// Loads new room data into the given RoomStruct object
void load_room(const String &filename, RoomStruct *room, bool game_is_hires, const std::vector<SpriteInfo> &sprinfos);
// Checks if it's necessary and upscales low-res room backgrounds and masks for the high resolution game
// NOTE: it does not upscale object coordinates, because that is usually done when the room is loaded
void UpscaleRoomBackground(RoomStruct *room, bool game_is_hires);
// Ensures that all existing room masks match room background size and
// MaskResolution property, resizes mask bitmaps if necessary.
void FixRoomMasks(RoomStruct *room);
// Adjusts bitmap size if necessary and returns either new or old bitmap.
PBitmap FixBitmap(PBitmap bmp, int dst_width, int dst_height);
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,99 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// Room version constants and information
//
//=============================================================================
#ifndef AGS_SHARED_GAME_ROOM_VERSION_H
#define AGS_SHARED_GAME_ROOM_VERSION_H
namespace AGS3 {
/* room file versions history
8: final v1.14 release
9: intermediate v2 alpha releases
10: v2 alpha-7 release
11: final v2.00 release
12: v2.08, to add colour depth byte
13: v2.14, add walkarea light levels
14: v2.4, fixed so it saves walkable area 15
15: v2.41, supports NewInteraction
16: v2.5
17: v2.5 - just version change to force room re-compile for new charctr struct
18: v2.51 - vector scaling
19: v2.53 - interaction variables
20: v2.55 - shared palette backgrounds
21: v2.55 - regions
22: v2.61 - encrypt room messages
23: v2.62 - object flags
24: v2.7 - hotspot script names
25: v2.72 - game id embedded
26: v3.0 - new interaction format, and no script source
27: v3.0 - store Y of bottom of object, not top
28: v3.0.3 - remove hotspot name length limit
29: v3.0.3 - high-res coords for object x/y, edges and hotspot walk-to point
30: v3.4.0.4 - tint luminance for regions
31: v3.4.1.5 - removed room object and hotspot name length limits
32: v3.5.0 - 64-bit file offsets
33: v3.5.0.8 - deprecated room resolution, added mask resolution
Since then format value is defined as AGS version represented as NN,NN,NN,NN.
*/
enum RoomFileVersion {
kRoomVersion_Undefined = 0,
kRoomVersion_pre114_3 = 3, // exact version unknown
kRoomVersion_pre114_4 = 4, // exact version unknown
kRoomVersion_pre114_5 = 5, // exact version unknown
kRoomVersion_pre114_6 = 6, // exact version unknown
kRoomVersion_114 = 8,
kRoomVersion_200_alpha = 9,
kRoomVersion_200_alpha7 = 10,
kRoomVersion_200_final = 11,
kRoomVersion_208 = 12,
kRoomVersion_214 = 13,
kRoomVersion_240 = 14,
kRoomVersion_241 = 15,
kRoomVersion_250a = 16,
kRoomVersion_250b = 17,
kRoomVersion_251 = 18,
kRoomVersion_253 = 19,
kRoomVersion_255a = 20,
kRoomVersion_255b = 21,
kRoomVersion_261 = 22,
kRoomVersion_262 = 23,
kRoomVersion_270 = 24,
kRoomVersion_272 = 25,
kRoomVersion_300a = 26,
kRoomVersion_300b = 27,
kRoomVersion_303a = 28,
kRoomVersion_303b = 29,
kRoomVersion_3404 = 30,
kRoomVersion_3415 = 31,
kRoomVersion_350 = 32,
kRoomVersion_3508 = 33,
kRoomVersion_Current = kRoomVersion_3508
};
} // namespace AGS3
#endif

View File

@@ -0,0 +1,274 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/game/tra_file.h"
#include "ags/shared/ac/words_dictionary.h"
#include "ags/shared/debugging/out.h"
#include "ags/shared/util/data_ext.h"
#include "ags/shared/util/string_compat.h"
#include "ags/shared/util/string_utils.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
const char *TRASignature = "AGSTranslation";
String GetTraFileErrorText(TraFileErrorType err) {
switch (err) {
case kTraFileErr_NoError:
return "No error.";
case kTraFileErr_SignatureFailed:
return "Not an AGS translation file or an unsupported format.";
case kTraFileErr_FormatNotSupported:
return "Format version not supported.";
case kTraFileErr_GameIDMismatch:
return "Game ID does not match, translation is meant for a different game.";
case kTraFileErr_UnexpectedEOF:
return "Unexpected end of file.";
case kTraFileErr_UnknownBlockType:
return "Unknown block type.";
case kTraFileErr_BlockDataOverlapping:
return "Block data overlapping.";
default:
return "Unknown error.";
}
}
String GetTraBlockName(TraFileBlock id) {
switch (id) {
case kTraFblk_Dict: return "Dictionary";
case kTraFblk_GameID: return "GameID";
case kTraFblk_TextOpts: return "TextOpts";
default: return "unknown";
}
}
HError OpenTraFile(Stream *in) {
// Test the file signature
char sigbuf[16] = { 0 };
in->Read(sigbuf, 15);
if (ags_stricmp(TRASignature, sigbuf) != 0)
return new TraFileError(kTraFileErr_SignatureFailed);
return HError::None();
}
HError ReadTraBlock(Translation &tra, Stream *in, TraFileBlock block, const String &ext_id, soff_t /*block_len*/) {
switch (block) {
case kTraFblk_Dict: {
std::vector<char> buf;
// Read lines until we find zero-length key & value
while (true) {
String src_line = read_string_decrypt(in, buf);
String dst_line = read_string_decrypt(in, buf);
if (src_line.IsEmpty() || dst_line.IsEmpty())
break;
tra.Dict.insert(std::make_pair(src_line, dst_line));
}
return HError::None();
}
case kTraFblk_GameID:
{
char gamename[256];
tra.GameUid = in->ReadInt32();
read_string_decrypt(in, gamename, sizeof(gamename));
tra.GameName = gamename;
return HError::None();
}
case kTraFblk_TextOpts:
tra.NormalFont = in->ReadInt32();
tra.SpeechFont = in->ReadInt32();
tra.RightToLeft = in->ReadInt32();
return HError::None();
case kTraFblk_None:
// continue reading extensions with string ID
break;
default:
return new TraFileError(kTraFileErr_UnknownBlockType,
String::FromFormat("Type: %d, known range: %d - %d.", block, kTraFblk_Dict, kTraFblk_TextOpts));
}
if (ext_id.CompareNoCase("ext_sopts") == 0) {
StrUtil::ReadStringMap(tra.StrOptions, in);
return HError::None();
}
return new TraFileError(kTraFileErr_UnknownBlockType,
String::FromFormat("Type: %s", ext_id.GetCStr()));
}
// TRABlockReader reads whole TRA data, block by block
class TRABlockReader : public DataExtReader {
public:
TRABlockReader(Translation &tra, Stream *in)
: DataExtReader(in, kDataExt_NumID32 | kDataExt_File32)
, _tra(tra) {
}
// Reads only the Game ID block and stops
HError ReadGameID() {
HError err = FindOne(kTraFblk_GameID);
if (!err)
return err;
return ReadTraBlock(_tra, _in, kTraFblk_GameID, "", _blockLen);
}
private:
String GetOldBlockName(int block_id) const override {
return GetTraBlockName((TraFileBlock)block_id);
}
soff_t GetOverLeeway(int block_id) const override {
// TRA files made by pre-3.0 editors have a block length miscount by 1 byte
if (block_id == kTraFblk_GameID) return 1;
return 0;
}
HError ReadBlock(int block_id, const String &ext_id,
soff_t block_len, bool &read_next) override {
read_next = true;
return ReadTraBlock(_tra, _in, (TraFileBlock)block_id, ext_id, block_len);
}
Translation &_tra;
};
HError TestTraGameID(int game_uid, const String &game_name, Stream *in) {
HError err = OpenTraFile(in);
if (!err)
return err;
Translation tra;
TRABlockReader reader(tra, in);
err = reader.ReadGameID();
if (!err)
return err;
// Test the identifiers, if they are not present then skip the test
if ((tra.GameUid != 0 && (game_uid != tra.GameUid)) ||
(!tra.GameName.IsEmpty() && (game_name != tra.GameName)))
return new TraFileError(kTraFileErr_GameIDMismatch,
String::FromFormat("The translation is designed for '%s'", tra.GameName.GetCStr()));
return HError::None();
}
HError ReadTraData(Translation &tra, Stream *in) {
HError err = OpenTraFile(in);
if (!err)
return err;
TRABlockReader reader(tra, in);
return reader.Read();
}
// TODO: perhaps merge with encrypt/decrypt utilities
static const char *EncryptText(std::vector<char> &en_buf, const String &s) {
if (en_buf.size() < s.GetLength() + 1)
en_buf.resize(s.GetLength() + 1);
strncpy(&en_buf.front(), s.GetCStr(), s.GetLength() + 1);
encrypt_text(&en_buf.front());
return &en_buf.front();
}
// TODO: perhaps merge with encrypt/decrypt utilities
static const char *EncryptEmptyString(std::vector<char> &en_buf) {
en_buf[0] = 0;
encrypt_text(&en_buf.front());
return &en_buf.front();
}
void WriteGameID(const Translation &tra, Stream *out) {
std::vector<char> en_buf;
out->WriteInt32(tra.GameUid);
StrUtil::WriteString(EncryptText(en_buf, tra.GameName), tra.GameName.GetLength() + 1, out);
}
void WriteDict(const Translation &tra, Stream *out) {
std::vector<char> en_buf;
for (const auto &kv : tra.Dict) {
const String &src = kv._key;
const String &dst = kv._value;
if (!dst.IsNullOrSpace()) {
String unsrc = StrUtil::Unescape(src);
String undst = StrUtil::Unescape(dst);
StrUtil::WriteString(EncryptText(en_buf, unsrc), unsrc.GetLength() + 1, out);
StrUtil::WriteString(EncryptText(en_buf, undst), undst.GetLength() + 1, out);
}
}
// Write a pair of empty key/values
StrUtil::WriteString(EncryptEmptyString(en_buf), 1, out);
StrUtil::WriteString(EncryptEmptyString(en_buf), 1, out);
}
void WriteTextOpts(const Translation &tra, Stream *out) {
out->WriteInt32(tra.NormalFont);
out->WriteInt32(tra.SpeechFont);
out->WriteInt32(tra.RightToLeft);
}
static const Translation *writer_tra;
static void(*writer_writer)(const Translation &tra, Stream *out);
void WriteStrOptions(const Translation &tra, Stream *out) {
StrUtil::WriteStringMap(tra.StrOptions, out);
}
static void WriteTraBlockWriter(Stream *out) {
writer_writer(*writer_tra, out);
}
inline void WriteTraBlock(const Translation &tra, TraFileBlock block,
void(*writer)(const Translation &tra, Stream *out), Stream *out) {
writer_tra = &tra;
writer_writer = writer;
WriteExtBlock(block, WriteTraBlockWriter,
kDataExt_NumID32 | kDataExt_File32, out);
}
inline void WriteTraBlock(const Translation &tra, const String &ext_id,
void(*writer)(const Translation &tra, Stream *out), Stream *out) {
writer_tra = &tra;
writer_writer = writer;
WriteExtBlock(ext_id, WriteTraBlockWriter,
kDataExt_NumID32 | kDataExt_File32, out);
}
void WriteTraData(const Translation &tra, Stream *out) {
// Write header
out->Write(TRASignature, strlen(TRASignature) + 1);
// Write all blocks
WriteTraBlock(tra, kTraFblk_GameID, WriteGameID, out);
WriteTraBlock(tra, kTraFblk_Dict, WriteDict, out);
WriteTraBlock(tra, kTraFblk_TextOpts, WriteTextOpts, out);
WriteTraBlock(tra, "ext_sopts", WriteStrOptions, out);
// Write ending
out->WriteInt32(kTraFile_EOF);
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,89 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// This unit provides functions for reading compiled translation file.
//
//=============================================================================
#ifndef AGS_SHARED_GAME_TRA_FILE_H
#define AGS_SHARED_GAME_TRA_FILE_H
#include "ags/shared/util/error.h"
#include "ags/shared/util/stream.h"
#include "ags/shared/util/string_types.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
enum TraFileErrorType {
kTraFileErr_NoError,
kTraFileErr_SignatureFailed,
kTraFileErr_FormatNotSupported,
kTraFileErr_GameIDMismatch,
kTraFileErr_UnexpectedEOF,
kTraFileErr_UnknownBlockType,
kTraFileErr_BlockDataOverlapping,
};
enum TraFileBlock {
kTraFblk_None = 0,
kTraFblk_Dict = 1,
kTraFblk_GameID = 2,
kTraFblk_TextOpts = 3,
// End of data tag
kTraFile_EOF = -1
};
String GetTraFileErrorText(TraFileErrorType err);
String GetTraBlockName(TraFileBlock id);
typedef TypedCodeError<TraFileErrorType, GetTraFileErrorText> TraFileError;
struct Translation {
// Game identifiers, for matching the translation file with the game
int GameUid = 0;
String GameName;
// Translation dictionary in source/dest pairs
StringMap Dict;
// Localization parameters
int NormalFont = -1; // replacement for normal font, or -1 for default
int SpeechFont = -1; // replacement for speech font, or -1 for default
int RightToLeft = -1; // r2l text mode (0, 1), or -1 for default
StringMap StrOptions; // to store extended options with string values
};
// Parses translation data and tests whether it matches the given game
HError TestTraGameID(int game_uid, const String &game_name, Stream *in);
// Reads full translation data from the provided stream
HError ReadTraData(Translation &tra, Stream *in);
// Writes all translation data to the stream
void WriteTraData(const Translation &tra, Stream *out);
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,457 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/config-manager.h"
#include "common/savefile.h"
#include "common/system.h"
#include "ags/lib/aastr-0.1.1/aastr.h"
#include "ags/shared/gfx/allegro_bitmap.h"
#include "ags/shared/gfx/image.h"
namespace AGS3 {
extern void __my_setcolor(int *ctset, int newcol, int wantColDep);
namespace AGS {
namespace Shared {
Bitmap::Bitmap()
: _alBitmap(nullptr)
, _isDataOwner(false) {
}
Bitmap::Bitmap(int width, int height, int color_depth)
: _alBitmap(nullptr)
, _isDataOwner(false) {
Create(width, height, color_depth);
}
Bitmap::Bitmap(Bitmap *src, const Rect &rc)
: _alBitmap(nullptr)
, _isDataOwner(false) {
CreateSubBitmap(src, rc);
}
Bitmap::Bitmap(BITMAP *al_bmp, bool shared_data)
: _alBitmap(nullptr)
, _isDataOwner(false) {
WrapAllegroBitmap(al_bmp, shared_data);
}
Bitmap::~Bitmap() {
Destroy();
}
//=============================================================================
// Creation and destruction
//=============================================================================
bool Bitmap::Create(int width, int height, int color_depth) {
Destroy();
if (color_depth) {
_alBitmap = create_bitmap_ex(color_depth, width, height);
} else {
_alBitmap = create_bitmap(width, height);
}
_isDataOwner = true;
return _alBitmap != nullptr;
}
bool Bitmap::CreateTransparent(int width, int height, int color_depth) {
if (Create(width, height, color_depth)) {
clear_to_color(_alBitmap, bitmap_mask_color(_alBitmap));
return true;
}
return false;
}
bool Bitmap::CreateSubBitmap(Bitmap *src, const Rect &rc) {
Destroy();
_alBitmap = create_sub_bitmap(src->_alBitmap, rc.Left, rc.Top, rc.GetWidth(), rc.GetHeight());
_isDataOwner = true;
return _alBitmap != nullptr;
}
bool Bitmap::ResizeSubBitmap(int width, int height) {
if (!isSubBitmap())
return false;
// TODO: can't clamp to parent size, because subs do not keep parent ref;
// might require amending allegro bitmap struct
_alBitmap->w = _alBitmap->cr = width;
_alBitmap->h = _alBitmap->cb = height;
return true;
}
bool Bitmap::CreateCopy(Bitmap *src, int color_depth) {
if (Create(src->_alBitmap->w, src->_alBitmap->h, color_depth ? color_depth : bitmap_color_depth(src->_alBitmap))) {
blit(src->_alBitmap, _alBitmap, 0, 0, 0, 0, _alBitmap->w, _alBitmap->h);
return true;
}
return false;
}
bool Bitmap::WrapAllegroBitmap(BITMAP *al_bmp, bool shared_data) {
Destroy();
_alBitmap = al_bmp;
_isDataOwner = !shared_data;
return _alBitmap != nullptr;
}
void Bitmap::Destroy() {
if (_isDataOwner && _alBitmap) {
destroy_bitmap(_alBitmap);
}
_alBitmap = nullptr;
_isDataOwner = false;
}
bool Bitmap::LoadFromFile(const char *filename) {
Destroy();
BITMAP *al_bmp = load_bitmap(filename, nullptr);
if (al_bmp) {
_alBitmap = al_bmp;
_isDataOwner = true;
}
return _alBitmap != nullptr;
}
bool Bitmap::LoadFromFile(PACKFILE *pf) {
Destroy();
BITMAP *al_bmp = load_bitmap(pf, nullptr);
if (al_bmp) {
_alBitmap = al_bmp;
_isDataOwner = true;
}
return _alBitmap != nullptr;
}
bool Bitmap::SaveToFile(Common::WriteStream &out, const void *palette) {
return save_bitmap(out, _alBitmap, (const RGB *)palette);
}
bool Bitmap::SaveToFile(const char *filename, const void *palette) {
// Only keeps the file name and add the game target as prefix.
Common::String name = filename;
size_t lastSlash = name.findLastOf('/');
if (lastSlash != Common::String::npos)
name = name.substr(lastSlash + 1);
Common::String gameTarget = ConfMan.getActiveDomainName();
if (!name.hasPrefixIgnoreCase(gameTarget))
name = gameTarget + "-" + name;
Common::OutSaveFile *out = g_system->getSavefileManager()->openForSaving(name, false);
assert(out);
bool result = SaveToFile(*out, palette);
out->finalize();
delete out;
return result;
}
color_t Bitmap::GetCompatibleColor(color_t color) {
color_t compat_color = 0;
__my_setcolor(&compat_color, color, bitmap_color_depth(_alBitmap));
return compat_color;
}
//=============================================================================
// Clipping
//=============================================================================
void Bitmap::SetClip(const Rect &rc) {
set_clip_rect(_alBitmap, rc.Left, rc.Top, rc.Right, rc.Bottom);
}
void Bitmap::ResetClip() {
set_clip_rect(_alBitmap, 0, 0, _alBitmap->w - 1, _alBitmap->h - 1);
}
Rect Bitmap::GetClip() const {
Rect temp;
get_clip_rect(_alBitmap, &temp.Left, &temp.Top, &temp.Right, &temp.Bottom);
return temp;
}
//=============================================================================
// Blitting operations (drawing one bitmap over another)
//=============================================================================
void Bitmap::Blit(Bitmap *src, int dst_x, int dst_y, BitmapMaskOption mask) {
BITMAP *al_src_bmp = src->_alBitmap;
// WARNING: For some evil reason Allegro expects dest and src bitmaps in different order for blit and draw_sprite
if (mask == kBitmap_Transparency) {
draw_sprite(_alBitmap, al_src_bmp, dst_x, dst_y);
} else {
blit(al_src_bmp, _alBitmap, 0, 0, dst_x, dst_y, al_src_bmp->w, al_src_bmp->h);
}
}
void Bitmap::Blit(Bitmap *src, int src_x, int src_y, int dst_x, int dst_y, int width, int height, BitmapMaskOption mask) {
BITMAP *al_src_bmp = src->_alBitmap;
if (mask == kBitmap_Transparency) {
masked_blit(al_src_bmp, _alBitmap, src_x, src_y, dst_x, dst_y, width, height);
} else {
blit(al_src_bmp, _alBitmap, src_x, src_y, dst_x, dst_y, width, height);
}
}
void Bitmap::MaskedBlit(Bitmap *src, int dst_x, int dst_y) {
draw_sprite(_alBitmap, src->_alBitmap, dst_x, dst_y);
}
void Bitmap::StretchBlt(Bitmap *src, const Rect &dst_rc, BitmapMaskOption mask) {
BITMAP *al_src_bmp = src->_alBitmap;
// WARNING: For some evil reason Allegro expects dest and src bitmaps in different order for blit and draw_sprite
if (mask == kBitmap_Transparency) {
stretch_sprite(_alBitmap, al_src_bmp,
dst_rc.Left, dst_rc.Top, dst_rc.GetWidth(), dst_rc.GetHeight());
} else {
stretch_blit(al_src_bmp, _alBitmap,
0, 0, al_src_bmp->w, al_src_bmp->h,
dst_rc.Left, dst_rc.Top, dst_rc.GetWidth(), dst_rc.GetHeight());
}
}
void Bitmap::StretchBlt(Bitmap *src, const Rect &src_rc, const Rect &dst_rc, BitmapMaskOption mask) {
BITMAP *al_src_bmp = src->_alBitmap;
if (mask == kBitmap_Transparency) {
masked_stretch_blit(al_src_bmp, _alBitmap,
src_rc.Left, src_rc.Top, src_rc.GetWidth(), src_rc.GetHeight(),
dst_rc.Left, dst_rc.Top, dst_rc.GetWidth(), dst_rc.GetHeight());
} else {
stretch_blit(al_src_bmp, _alBitmap,
src_rc.Left, src_rc.Top, src_rc.GetWidth(), src_rc.GetHeight(),
dst_rc.Left, dst_rc.Top, dst_rc.GetWidth(), dst_rc.GetHeight());
}
}
void Bitmap::AAStretchBlt(Bitmap *src, const Rect &dst_rc, BitmapMaskOption mask) {
BITMAP *al_src_bmp = src->_alBitmap;
// WARNING: For some evil reason Allegro expects dest and src bitmaps in different order for blit and draw_sprite
if (mask == kBitmap_Transparency) {
aa_stretch_sprite(_alBitmap, al_src_bmp,
dst_rc.Left, dst_rc.Top, dst_rc.GetWidth(), dst_rc.GetHeight());
} else {
aa_stretch_blit(al_src_bmp, _alBitmap,
0, 0, al_src_bmp->w, al_src_bmp->h,
dst_rc.Left, dst_rc.Top, dst_rc.GetWidth(), dst_rc.GetHeight());
}
}
void Bitmap::AAStretchBlt(Bitmap *src, const Rect &src_rc, const Rect &dst_rc, BitmapMaskOption mask) {
BITMAP *al_src_bmp = src->_alBitmap;
if (mask == kBitmap_Transparency) {
// TODO: aastr lib does not expose method for masked stretch blit; should do that at some point since
// the source code is a gift-ware anyway
// aa_masked_blit(_alBitmap, al_src_bmp, src_rc.Left, src_rc.Top, src_rc.GetWidth(), src_rc.GetHeight(), dst_rc.Left, dst_rc.Top, dst_rc.GetWidth(), dst_rc.GetHeight());
error("aa_masked_blit is not yet supported!");
} else {
aa_stretch_blit(al_src_bmp, _alBitmap,
src_rc.Left, src_rc.Top, src_rc.GetWidth(), src_rc.GetHeight(),
dst_rc.Left, dst_rc.Top, dst_rc.GetWidth(), dst_rc.GetHeight());
}
}
void Bitmap::TransBlendBlt(Bitmap *src, int dst_x, int dst_y) {
BITMAP *al_src_bmp = src->_alBitmap;
draw_trans_sprite(_alBitmap, al_src_bmp, dst_x, dst_y);
}
void Bitmap::LitBlendBlt(Bitmap *src, int dst_x, int dst_y, int light_amount) {
BITMAP *al_src_bmp = src->_alBitmap;
draw_lit_sprite(_alBitmap, al_src_bmp, dst_x, dst_y, light_amount);
}
void Bitmap::FlipBlt(Bitmap *src, int dst_x, int dst_y, GraphicFlip flip) {
BITMAP *al_src_bmp = src->_alBitmap;
switch (flip) {
case kFlip_Horizontal:
draw_sprite_h_flip(_alBitmap, al_src_bmp, dst_x, dst_y);
break;
case kFlip_Vertical:
draw_sprite_v_flip(_alBitmap, al_src_bmp, dst_x, dst_y);
break;
case kFlip_Both:
draw_sprite_vh_flip(_alBitmap, al_src_bmp, dst_x, dst_y);
break;
default: // blit with no transform
Blit(src, dst_x, dst_y);
break;
}
}
void Bitmap::RotateBlt(Bitmap *src, int dst_x, int dst_y, fixed_t angle) {
BITMAP *al_src_bmp = src->_alBitmap;
rotate_sprite(_alBitmap, al_src_bmp, dst_x, dst_y, angle);
}
void Bitmap::RotateBlt(Bitmap *src, int dst_x, int dst_y, int pivot_x, int pivot_y, fixed_t angle) {
BITMAP *al_src_bmp = src->_alBitmap;
pivot_sprite(_alBitmap, al_src_bmp, dst_x, dst_y, pivot_x, pivot_y, angle);
}
//=============================================================================
// Pixel operations
//=============================================================================
void Bitmap::Clear(color_t color) {
if (color) {
clear_to_color(_alBitmap, color);
} else {
clear_bitmap(_alBitmap);
}
}
void Bitmap::ClearTransparent() {
clear_to_color(_alBitmap, bitmap_mask_color(_alBitmap));
}
void Bitmap::PutPixel(int x, int y, color_t color) {
if (x < 0 || x >= _alBitmap->w || y < 0 || y >= _alBitmap->h) {
return;
}
switch (bitmap_color_depth(_alBitmap)) {
case 8:
return _putpixel(_alBitmap, x, y, color);
case 15:
return _putpixel15(_alBitmap, x, y, color);
case 16:
return _putpixel16(_alBitmap, x, y, color);
case 24:
return _putpixel24(_alBitmap, x, y, color);
case 32:
return _putpixel32(_alBitmap, x, y, color);
}
assert(0); // this should not normally happen
return putpixel(_alBitmap, x, y, color);
}
int Bitmap::GetPixel(int x, int y) const {
if (x < 0 || x >= _alBitmap->w || y < 0 || y >= _alBitmap->h) {
return -1; // Allegros getpixel() implementation returns -1 in this case
}
switch (bitmap_color_depth(_alBitmap)) {
case 8:
return _getpixel(_alBitmap, x, y);
case 15:
return _getpixel15(_alBitmap, x, y);
case 16:
return _getpixel16(_alBitmap, x, y);
case 24:
return _getpixel24(_alBitmap, x, y);
case 32:
return _getpixel32(_alBitmap, x, y);
}
assert(0); // this should not normally happen
return getpixel(_alBitmap, x, y);
}
//=============================================================================
// Vector drawing operations
//=============================================================================
void Bitmap::DrawLine(const Line &ln, color_t color) {
line(_alBitmap, ln.X1, ln.Y1, ln.X2, ln.Y2, color);
}
void Bitmap::DrawTriangle(const Triangle &tr, color_t color) {
triangle(_alBitmap,
tr.X1, tr.Y1, tr.X2, tr.Y2, tr.X3, tr.Y3, color);
}
void Bitmap::DrawRect(const Rect &rc, color_t color) {
rect(_alBitmap, rc.Left, rc.Top, rc.Right, rc.Bottom, color);
}
void Bitmap::FillRect(const Rect &rc, color_t color) {
rectfill(_alBitmap, rc.Left, rc.Top, rc.Right, rc.Bottom, color);
}
void Bitmap::FillCircle(const Circle &circle, color_t color) {
circlefill(_alBitmap, circle.X, circle.Y, circle.Radius, color);
}
void Bitmap::Fill(color_t color) {
if (color) {
clear_to_color(_alBitmap, color);
} else {
clear_bitmap(_alBitmap);
}
}
void Bitmap::FillTransparent() {
clear_to_color(_alBitmap, bitmap_mask_color(_alBitmap));
}
void Bitmap::FloodFill(int x, int y, color_t color) {
_alBitmap->floodfill(x, y, color);
}
//=============================================================================
// Direct access operations
//=============================================================================
void Bitmap::SetScanLine(int index, unsigned char *data, int data_size) {
if (index < 0 || index >= GetHeight()) {
return;
}
int copy_length = data_size;
if (copy_length < 0) {
copy_length = GetLineLength();
} else // TODO: use Math namespace here
if (copy_length > GetLineLength()) {
copy_length = GetLineLength();
}
memcpy(_alBitmap->line[index], data, copy_length);
}
namespace BitmapHelper {
Bitmap *CreateRawBitmapOwner(BITMAP *al_bmp) {
Bitmap *bitmap = new Bitmap();
if (!bitmap->WrapAllegroBitmap(al_bmp, false)) {
delete bitmap;
bitmap = nullptr;
}
return bitmap;
}
Bitmap *CreateRawBitmapWrapper(BITMAP *al_bmp) {
Bitmap *bitmap = new Bitmap();
if (!bitmap->WrapAllegroBitmap(al_bmp, true)) {
delete bitmap;
bitmap = nullptr;
}
return bitmap;
}
} // namespace BitmapHelper
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,277 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// Allegro lib based bitmap
//
// TODO: probably should be moved to the Engine; check again when (if) it is
// clear that AGS.Native does not need allegro for drawing.
//
//=============================================================================
#ifndef AGS_SHARED_GFX_ALLEGRO_BITMAP_H
#define AGS_SHARED_GFX_ALLEGRO_BITMAP_H
#include "graphics/screen.h"
#include "ags/lib/allegro.h" // BITMAP
#include "ags/shared/core/types.h"
#include "ags/shared/gfx/bitmap.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class Bitmap {
public:
Bitmap();
Bitmap(int width, int height, int color_depth = 0);
Bitmap(Bitmap *src, const Rect &rc);
Bitmap(BITMAP *al_bmp, bool shared_data);
~Bitmap();
// Allocate new bitmap.
// NOTE: color_depth is in BITS per pixel (i.e. 8, 16, 24, 32...).
// NOTE: in all of these color_depth may be passed as 0 in which case a default
// color depth will be used (as previously set for the system).
// TODO: color_depth = 0 is used to call Allegro's create_bitmap, which uses
// some global color depth setting; not sure if this is OK to use for generic class,
// revise this in future
bool Create(int width, int height, int color_depth = 0);
// Create Bitmap and clear to transparent color
bool CreateTransparent(int width, int height, int color_depth = 0);
// Creates a sub-bitmap of the given bitmap; the sub-bitmap is a reference to
// particular region inside a parent.
// WARNING: the parent bitmap MUST be kept in memory for as long as sub-bitmap exists!
bool CreateSubBitmap(Bitmap *src, const Rect &rc);
// Resizes existing sub-bitmap within the borders of its parent
bool ResizeSubBitmap(int width, int height);
// Creates a plain copy of the given bitmap, optionally converting to a different color depth;
// pass color depth 0 to keep the original one.
bool CreateCopy(Bitmap *src, int color_depth = 0);
// TODO: this is a temporary solution for plugin support
// Wraps a raw allegro BITMAP object, optionally owns it (will delete on disposal)
bool WrapAllegroBitmap(BITMAP *al_bmp, bool shared_data);
// Deallocate bitmap
void Destroy();
bool LoadFromFile(const String &filename) {
return LoadFromFile(filename.GetCStr());
}
bool LoadFromFile(const char *filename);
bool LoadFromFile(PACKFILE *pf);
bool SaveToFile(const String &filename, const void *palette) {
return SaveToFile(filename.GetCStr(), palette);
}
bool SaveToFile(Common::WriteStream &out, const void *palette);
bool SaveToFile(const char *filename, const void *palette);
// TODO: This is temporary solution for cases when we cannot replace
// use of raw BITMAP struct with Bitmap
inline BITMAP *GetAllegroBitmap() {
return _alBitmap;
}
// Is this a "normal" bitmap created by application which data can be directly accessed for reading and writing
inline bool IsMemoryBitmap() const {
return true;
}
// Is this a video bitmap
inline bool IsVideoBitmap() const {
return dynamic_cast<Graphics::Screen *>(_alBitmap) != nullptr;
}
// Is this a linear bitmap, the one that can be accessed linearly within each scanline
inline bool IsLinearBitmap() const {
return true;
}
// Is this a subbitmap, referencing a part of another, bigger one?
inline bool isSubBitmap() const {
return _alBitmap->isSubBitmap();
}
// Do both bitmaps share same data (usually: subbitmaps, or parent/subbitmap)
inline bool IsSameBitmap(Bitmap *other) const {
return is_same_bitmap(_alBitmap, other->_alBitmap) != 0;
}
// Checks if bitmap cannot be used
inline bool IsNull() const {
return !_alBitmap;
}
// Checks if bitmap has zero size: either width or height (or both) is zero
inline bool IsEmpty() const {
return GetWidth() == 0 || GetHeight() == 0;
}
inline int GetWidth() const {
return _alBitmap->w;
}
inline int GetHeight() const {
return _alBitmap->h;
}
inline Size GetSize() const {
return Size(_alBitmap->w, _alBitmap->h);
}
// Get sub-bitmap's offset position within its parent
inline Point GetSubOffset() const {
Common::Point pt = _alBitmap->getOffsetFromOwner();
return Point(pt.x, pt.y);
}
inline int GetColorDepth() const {
return bitmap_color_depth(_alBitmap);
}
// BPP: bytes per pixel
inline int GetBPP() const {
return (GetColorDepth() + 7) / 8;
}
// CHECKME: probably should not be exposed, see comment to GetData()
inline int GetDataSize() const {
return GetWidth() * GetHeight() * GetBPP();
}
// Gets scanline length in bytes (is the same for any scanline)
inline int GetLineLength() const {
return GetWidth() * GetBPP();
}
// TODO: replace with byte *
// Gets a pointer to underlying graphic data
// FIXME: actually not a very good idea, since there's no 100% guarantee the scanline positions in memory are sequential
inline const unsigned char *GetData() const {
return _alBitmap->getPixels();
}
// Get scanline for direct reading
inline const unsigned char *GetScanLine(int index) const {
assert(index >= 0 && index < GetHeight());
return _alBitmap->getBasePtr(0, index);
}
inline unsigned char *GetScanLine(int index) {
assert(index >= 0 && index < GetHeight());
return (unsigned char *)_alBitmap->getBasePtr(0, index);
}
// Get bitmap's mask color (transparent color)
inline color_t GetMaskColor() const {
return bitmap_mask_color(_alBitmap);
}
// Converts AGS color-index into RGB color according to the bitmap format.
// TODO: this method was added to the Bitmap class during large refactoring,
// but that's a mistake, because in retrospect is has nothing to do with
// bitmap itself and should rather be a part of the game data logic.
color_t GetCompatibleColor(color_t color);
//=========================================================================
// Clipping
// TODO: consider implementing push-pop clipping stack logic.
//=========================================================================
void SetClip(const Rect &rc);
void ResetClip();
Rect GetClip() const;
//=========================================================================
// Blitting operations (drawing one bitmap over another)
//=========================================================================
// Draw other bitmap over current one
void Blit(Bitmap *src, int dst_x = 0, int dst_y = 0, BitmapMaskOption mask = kBitmap_Copy);
void Blit(Bitmap *src, int src_x, int src_y, int dst_x, int dst_y, int width, int height, BitmapMaskOption mask = kBitmap_Copy);
// Draw other bitmap in a masked mode (kBitmap_Transparency)
void MaskedBlit(Bitmap *src, int dst_x, int dst_y);
// Draw other bitmap, stretching or shrinking its size to given values
void StretchBlt(Bitmap *src, const Rect &dst_rc, BitmapMaskOption mask = kBitmap_Copy);
void StretchBlt(Bitmap *src, const Rect &src_rc, const Rect &dst_rc, BitmapMaskOption mask = kBitmap_Copy);
// Antia-aliased stretch-blit
void AAStretchBlt(Bitmap *src, const Rect &dst_rc, BitmapMaskOption mask = kBitmap_Copy);
void AAStretchBlt(Bitmap *src, const Rect &src_rc, const Rect &dst_rc, BitmapMaskOption mask = kBitmap_Copy);
// TODO: find more general way to call these operations, probably require pointer to Blending data struct?
// Draw bitmap using translucency preset
void TransBlendBlt(Bitmap *src, int dst_x, int dst_y);
// Draw bitmap using lighting preset
void LitBlendBlt(Bitmap *src, int dst_x, int dst_y, int light_amount);
// TODO: generic "draw transformed" function? What about mask option?
void FlipBlt(Bitmap *src, int dst_x, int dst_y, GraphicFlip flip);
void RotateBlt(Bitmap *src, int dst_x, int dst_y, fixed_t angle);
void RotateBlt(Bitmap *src, int dst_x, int dst_y, int pivot_x, int pivot_y, fixed_t angle);
//=========================================================================
// Pixel operations
//=========================================================================
// Fills the whole bitmap with given color (black by default)
void Clear(color_t color = 0);
void ClearTransparent();
// The PutPixel and GetPixel are supposed to be safe and therefore
// relatively slow operations. They should not be used for changing large
// blocks of bitmap memory - reading/writing from/to scan lines should be
// done in such cases.
void PutPixel(int x, int y, color_t color);
int GetPixel(int x, int y) const;
//=========================================================================
// Vector drawing operations
//=========================================================================
void DrawLine(const Line &ln, color_t color);
void DrawTriangle(const Triangle &tr, color_t color);
void DrawRect(const Rect &rc, color_t color);
void FillRect(const Rect &rc, color_t color);
void FillCircle(const Circle &circle, color_t color);
// Fills the whole bitmap with given color
void Fill(color_t color);
void FillTransparent();
// Floodfills an enclosed area, starting at point
void FloodFill(int x, int y, color_t color);
//=========================================================================
// Direct access operations
//=========================================================================
// TODO: think how to increase safety over this (some fixed memory buffer class with iterator?)
// Gets scanline for directly writing into it
inline unsigned char *GetScanLineForWriting(int index) {
assert(index >= 0 && index < GetHeight());
return _alBitmap->line[index];
}
inline unsigned char *GetDataForWriting() {
return _alBitmap->line[0];
}
// Copies buffer contents into scanline
void SetScanLine(int index, unsigned char *data, int data_size = -1);
private:
BITMAP *_alBitmap;
bool _isDataOwner;
};
namespace BitmapHelper {
// TODO: revise those functions later (currently needed in a few very specific cases)
// NOTE: the resulting object __owns__ bitmap data from now on
Bitmap *CreateRawBitmapOwner(BITMAP *al_bmp);
// NOTE: the resulting object __does not own__ bitmap data
Bitmap *CreateRawBitmapWrapper(BITMAP *al_bmp);
} // namespace BitmapHelper
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,240 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/gfx/bitmap.h"
#include "ags/shared/util/memory.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
// TODO: revise this construction later
namespace BitmapHelper {
Bitmap *CreateBitmap(int width, int height, int color_depth) {
Bitmap *bitmap = new Bitmap();
if (!bitmap->Create(width, height, color_depth)) {
delete bitmap;
bitmap = nullptr;
}
return bitmap;
}
Bitmap *CreateClearBitmap(int width, int height, int color_depth, int clear_color) {
Bitmap *bitmap = new Bitmap();
if (!bitmap->Create(width, height, color_depth)) {
delete bitmap;
return nullptr;
}
bitmap->Clear(clear_color);
return bitmap;
}
Bitmap *CreateTransparentBitmap(int width, int height, int color_depth) {
Bitmap *bitmap = new Bitmap();
if (!bitmap->CreateTransparent(width, height, color_depth)) {
delete bitmap;
bitmap = nullptr;
}
return bitmap;
}
Bitmap *CreateSubBitmap(Bitmap *src, const Rect &rc) {
Bitmap *bitmap = new Bitmap();
if (!bitmap->CreateSubBitmap(src, rc)) {
delete bitmap;
bitmap = nullptr;
}
return bitmap;
}
Bitmap *CreateBitmapCopy(Bitmap *src, int color_depth) {
Bitmap *bitmap = new Bitmap();
if (!bitmap->CreateCopy(src, color_depth)) {
delete bitmap;
bitmap = nullptr;
}
return bitmap;
}
Bitmap *LoadFromFile(const char *filename) {
Bitmap *bitmap = new Bitmap();
if (!bitmap->LoadFromFile(filename)) {
delete bitmap;
bitmap = nullptr;
}
return bitmap;
}
Bitmap *LoadFromFile(PACKFILE *pf) {
Bitmap *bitmap = new Bitmap();
if (!bitmap->LoadFromFile(pf)) {
delete bitmap;
bitmap = nullptr;
}
return bitmap;
}
Bitmap *AdjustBitmapSize(Bitmap *src, int width, int height) {
int oldw = src->GetWidth(), oldh = src->GetHeight();
if ((oldw == width) && (oldh == height))
return src;
Bitmap *bmp = BitmapHelper::CreateBitmap(width, height, src->GetColorDepth());
bmp->StretchBlt(src, RectWH(0, 0, oldw, oldh), RectWH(0, 0, width, height));
return bmp;
}
void MakeOpaque(Bitmap *bmp) {
if (bmp->GetColorDepth() < 32)
return; // no alpha channel
for (int i = 0; i < bmp->GetHeight(); ++i) {
uint32_t *line = reinterpret_cast<uint32_t *>(bmp->GetScanLineForWriting(i));
uint32_t *line_end = line + bmp->GetWidth();
for (uint32_t *px = line; px != line_end; ++px)
*px = makeacol32(getr32(*px), getg32(*px), getb32(*px), 255);
}
}
void MakeOpaqueSkipMask(Bitmap *bmp) {
if (bmp->GetColorDepth() < 32)
return; // no alpha channel
for (int i = 0; i < bmp->GetHeight(); ++i) {
uint32_t *line = reinterpret_cast<uint32_t *>(bmp->GetScanLineForWriting(i));
uint32_t *line_end = line + bmp->GetWidth();
for (uint32_t *px = line; px != line_end; ++px)
if (*px != MASK_COLOR_32)
*px = makeacol32(getr32(*px), getg32(*px), getb32(*px), 255);
}
}
void ReplaceAlphaWithRGBMask(Bitmap *bmp) {
if (bmp->GetColorDepth() < 32)
return; // no alpha channel
for (int i = 0; i < bmp->GetHeight(); ++i) {
uint32_t *line = reinterpret_cast<uint32_t *>(bmp->GetScanLineForWriting(i));
uint32_t *line_end = line + bmp->GetWidth();
for (uint32_t *px = line; px != line_end; ++px)
if (geta32(*px) == 0)
*px = MASK_COLOR_32;
}
}
// Functor that copies the "mask color" pixels from source to dest
template <class TPx, size_t BPP_>
struct PixelTransCpy {
static const size_t BPP = BPP_;
inline void operator ()(uint8_t *dst, const uint8_t *src, uint32_t mask_color, bool /*use_alpha*/) const {
if (*(const TPx *)src == mask_color)
*(TPx *)dst = mask_color;
}
};
// Functor that tells to never skip a pixel in the mask
struct PixelNoSkip {
inline bool operator ()(uint8_t * /*data*/, uint32_t /*mask_color*/, bool /*use_alpha*/) const {
return false;
}
};
typedef PixelTransCpy<uint8_t, 1> PixelTransCpy8;
typedef PixelTransCpy<uint16_t, 2> PixelTransCpy16;
// Functor that copies the "mask color" pixels from source to dest, 24-bit depth
struct PixelTransCpy24 {
static const size_t BPP = 3;
inline void operator ()(uint8_t *dst, const uint8_t *src, uint32_t mask_color, bool /*use_alpha*/) const {
const uint8_t *mcol_ptr = (const uint8_t *)&mask_color;
if (src[0] == mcol_ptr[0] && src[1] == mcol_ptr[1] && src[2] == mcol_ptr[2]) {
dst[0] = mcol_ptr[0];
dst[1] = mcol_ptr[1];
dst[2] = mcol_ptr[2];
}
}
};
// Functor that copies the "mask color" pixels from source to dest, 32-bit depth, with alpha
struct PixelTransCpy32 {
static const size_t BPP = 4;
inline void operator ()(uint8_t *dst, const uint8_t *src, uint32_t mask_color, bool use_alpha) const {
if (*(const uint32_t *)src == mask_color)
*(uint32_t *)dst = mask_color;
else if (use_alpha)
dst[3] = src[3]; // copy alpha channel
else
dst[3] = 0xFF; // set the alpha channel byte to opaque
}
};
// Functor that tells to skip pixels if they match the mask color or have alpha = 0
struct PixelTransSkip32 {
inline bool operator ()(uint8_t *data, uint32_t mask_color, bool use_alpha) const {
return *(const uint32_t *)data == mask_color || (use_alpha && data[3] == 0);
}
};
// Applies bitmap mask, using 2 functors:
// - one that tells whether to skip current pixel;
// - another that copies the color from src to dest
template <class FnPxProc, class FnSkip>
void ApplyMask(uint8_t *dst, const uint8_t *src, size_t pitch, size_t height,
FnPxProc proc, FnSkip skip, uint32_t mask_color, bool dst_has_alpha, bool mask_has_alpha) {
for (size_t y = 0; y < height; ++y) {
for (size_t x = 0; x < pitch; x += FnPxProc::BPP, src += FnPxProc::BPP, dst += FnPxProc::BPP) {
if (!skip(dst, mask_color, dst_has_alpha))
proc(dst, src, mask_color, mask_has_alpha);
}
}
}
void CopyTransparency(Bitmap *dst, const Bitmap *mask, bool dst_has_alpha, bool mask_has_alpha) {
color_t mask_color = mask->GetMaskColor();
uint8_t *dst_ptr = dst->GetDataForWriting();
const uint8_t *src_ptr = mask->GetData();
const size_t bpp = mask->GetBPP();
const size_t pitch = mask->GetLineLength();
const size_t height = mask->GetHeight();
if (bpp == 1)
ApplyMask(dst_ptr, src_ptr, pitch, height, PixelTransCpy8(), PixelNoSkip(), mask_color, dst_has_alpha, mask_has_alpha);
else if (bpp == 2)
ApplyMask(dst_ptr, src_ptr, pitch, height, PixelTransCpy16(), PixelNoSkip(), mask_color, dst_has_alpha, mask_has_alpha);
else if (bpp == 3)
ApplyMask(dst_ptr, src_ptr, pitch, height, PixelTransCpy24(), PixelNoSkip(), mask_color, dst_has_alpha, mask_has_alpha);
else
ApplyMask(dst_ptr, src_ptr, pitch, height, PixelTransCpy32(), PixelTransSkip32(), mask_color, dst_has_alpha, mask_has_alpha);
}
void ReadPixelsFromMemory(Bitmap *dst, const uint8_t *src_buffer, const size_t src_pitch, const size_t src_px_offset) {
const size_t bpp = dst->GetBPP();
const size_t src_px_pitch = src_pitch / bpp;
if (src_px_offset >= src_px_pitch)
return; // nothing to copy
Memory::BlockCopy(dst->GetDataForWriting(), dst->GetLineLength(), 0, src_buffer, src_pitch, src_px_offset * bpp, dst->GetHeight());
}
} // namespace BitmapHelper
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,114 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// Base bitmap header
//
//=============================================================================
#ifndef AGS_SHARED_GFX_BITMAP_H
#define AGS_SHARED_GFX_BITMAP_H
#include "ags/shared/gfx/gfx_def.h"
#include "ags/shared/util/geometry.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
// Mask option for blitting one bitmap on another
enum BitmapMaskOption {
// Plain copies bitmap pixels
kBitmap_Copy,
// Consider mask color fully transparent and do not copy pixels having it
kBitmap_Transparency
};
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#include "ags/shared/gfx/allegro_bitmap.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class Bitmap;
// TODO: revise this construction later
namespace BitmapHelper {
// Helper functions, that delete faulty bitmaps automatically, and return
// NULL if bitmap could not be created.
// NOTE: color_depth is in BITS per pixel (i.e. 8, 16, 24, 32...).
// NOTE: in all of these color_depth may be passed as 0 in which case a default
// color depth will be used (as previously set for the system).
// Creates a new bitmap of the given format; the pixel contents are undefined.
Bitmap *CreateBitmap(int width, int height, int color_depth = 0);
// Creates a new bitmap and clears it with the given color
Bitmap *CreateClearBitmap(int width, int height, int color_depth = 0, int clear_color = 0);
// Creates a new bitmap and clears it with the transparent color
Bitmap *CreateTransparentBitmap(int width, int height, int color_depth = 0);
// Creates a sub-bitmap of the given bitmap; the sub-bitmap is a reference to
// particular region inside a parent.
// WARNING: the parent bitmap MUST be kept in memory for as long as sub-bitmap exists!
Bitmap *CreateSubBitmap(Bitmap *src, const Rect &rc);
// Creates a plain copy of the given bitmap, optionally converting to a different color depth;
// pass color depth 0 to keep the original one.
Bitmap *CreateBitmapCopy(Bitmap *src, int color_depth = 0);
// Load a bitmap from file; supported formats currently are: BMP, PCX.
Bitmap *LoadFromFile(const char *filename);
inline Bitmap *LoadFromFile(const String &filename) {
return LoadFromFile(filename.GetCStr());
}
Bitmap *LoadFromFile(PACKFILE *pf);
// Stretches bitmap to the requested size. The new bitmap will have same
// colour depth. Returns original bitmap if no changes are necessary.
Bitmap *AdjustBitmapSize(Bitmap *src, int width, int height);
// Makes the given bitmap opaque (full alpha), while keeping pixel RGB unchanged.
void MakeOpaque(Bitmap *bmp);
// Makes the given bitmap opaque (full alpha), while keeping pixel RGB unchanged.
// Skips mask color (leaves it with zero alpha).
void MakeOpaqueSkipMask(Bitmap *bmp);
// Replaces fully transparent (alpha = 0) pixels with standard mask color.
void ReplaceAlphaWithRGBMask(Bitmap *bmp);
// Copy transparency mask and/or alpha channel from one bitmap into another.
// Destination and mask bitmaps must be of the same pixel format.
// Transparency is merged, meaning that fully transparent pixels on
// destination should remain such regardless of mask pixel values.
void CopyTransparency(Bitmap *dst, const Bitmap *mask, bool dst_has_alpha, bool mask_has_alpha);
// Copy pixel data into bitmap from memory buffer. It is required that the
// source matches bitmap format and has enough data.
// Pitch is given in bytes and defines the length of the source scan line.
// Offset is optional and defines horizontal offset, in pixels.
void ReadPixelsFromMemory(Bitmap *dst, const uint8_t *src_buffer, const size_t src_pitch, const size_t src_px_offset = 0);
} // namespace BitmapHelper
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,152 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// Graphic definitions and type/unit conversions.
//
//=============================================================================
#ifndef AGS_SHARED_GFX_GFX_DEF_H
#define AGS_SHARED_GFX_GFX_DEF_H
namespace AGS3 {
namespace AGS {
namespace Shared {
enum GraphicFlip {
kFlip_None,
kFlip_Horizontal, // this means - mirror over horizontal middle line
kFlip_Vertical, // this means - mirror over vertical middle line
kFlip_Both // mirror over diagonal (horizontal and vertical)
};
enum BlendMode {
// free blending (ARGB -> ARGB) modes
kBlendMode_NoAlpha = 0, // ignore alpha channel
kBlendMode_Alpha, // alpha-blend src to dest, combining src & dest alphas
// NOTE: add new modes here
kNumBlendModes
};
namespace GfxDef {
// Converts percentage of transparency into alpha
inline int Trans100ToAlpha255(int transparency) {
return ((100 - transparency) * 255) / 100;
}
// Converts alpha into percentage of transparency
inline int Alpha255ToTrans100(int alpha) {
return 100 - ((alpha * 100) / 255);
}
// Special formulae to reduce precision loss and support flawless forth &
// reverse conversion for multiplies of 10%
inline int Trans100ToAlpha250(int transparency) {
return ((100 - transparency) * 25) / 10;
}
inline int Alpha250ToTrans100(int alpha) {
return 100 - ((alpha * 10) / 25);
}
// Convert correct 100-ranged transparency into legacy 255-ranged
// transparency; legacy inconsistent transparency value range:
// 0 = opaque,
// 255 = invisible,
// 1 -to- 254 = barely visible -to- mostly visible (as proper alpha)
inline int Trans100ToLegacyTrans255(int transparency) {
switch (transparency) {
case 0:
return 0; // this means opaque
case 100:
return 255; // this means invisible
default:
// the rest of the range works as alpha
return Trans100ToAlpha250(transparency);
}
}
// Convert legacy 255-ranged "incorrect" transparency into proper
// 100-ranged transparency.
inline int LegacyTrans255ToTrans100(int legacy_transparency) {
switch (legacy_transparency) {
case 0:
return 0; // this means opaque
case 255:
return 100; // this means invisible
default:
// the rest of the range works as alpha
return Alpha250ToTrans100(legacy_transparency);
}
}
// Convert legacy 100-ranged transparency into proper 255-ranged alpha
// 0 => alpha 255
// 100 => alpha 0
// 1 - 99 => alpha 1 - 244
inline int LegacyTrans100ToAlpha255(int legacy_transparency) {
switch (legacy_transparency) {
case 0:
return 255; // this means opaque
case 100:
return 0; // this means invisible
default:
// the rest of the range works as alpha (only 100-ranged)
return legacy_transparency * 255 / 100;
}
}
// Convert legacy 255-ranged transparency into proper 255-ranged alpha
inline int LegacyTrans255ToAlpha255(int legacy_transparency) {
switch (legacy_transparency) {
case 0:
return 255; // this means opaque
case 255:
return 0; // this means invisible
default:
// the rest of the range works as alpha
return legacy_transparency;
}
}
// Convert 255-ranged alpha into legacy 255-ranged transparency
inline int Alpha255ToLegacyTrans255(int alpha) {
switch (alpha) {
case 255:
return 0; // this means opaque
case 0:
return 255; // this means invisible
default:
// the rest of the range works as alpha
return alpha;
}
}
} // namespace GfxDef
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,151 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/gfx/image.h"
#include "ags/shared/util/file.h"
#include "ags/shared/util/stream.h"
#include "ags/lib/allegro.h"
#include "common/file.h"
#include "common/str.h"
#include "common/stream.h"
#include "common/textconsole.h"
#include "image/bmp.h"
#include "image/iff.h"
#include "image/pcx.h"
#include "image/tga.h"
#define VGA_COLOR_TRANS(x) ((x) * 255 / 63)
namespace AGS3 {
template<class DECODER>
BITMAP *decodeImageStream(Common::SeekableReadStream &stream, color *pal) {
DECODER decoder;
if (decoder.loadStream(stream)) {
// Create the output surface
const Graphics::Surface *src = decoder.getSurface();
// Copy the decoded surface
int bpp = 8 * src->format.bytesPerPixel;
if (bpp == 24)
bpp = 32;
Surface *dest = (Surface *)create_bitmap_ex(bpp, src->w, src->h);
dest->blitFrom(*src);
// Copy the palette
const Graphics::Palette &palP = decoder.getPalette();
if (pal) {
for (uint idx = 0; idx < palP.size(); ++idx) {
palP.get(idx, pal[idx].r, pal[idx].g, pal[idx].b);
pal[idx].filler = 0xff;
}
}
return dest;
} else {
return nullptr;
}
}
template<class DECODER>
BITMAP *decodeImage(const char *filename, color *pal) {
AGS::Shared::Stream *file = AGS3::AGS::Shared::File::OpenFileRead(filename);
if (!file)
return nullptr;
AGS::Shared::ScummVMReadStream f(file);
return decodeImageStream<DECODER>(f, pal);
}
template<class DECODER>
BITMAP *decodeImage(PACKFILE *pf, color *pal) {
if (!pf)
return nullptr;
AGS::Shared::ScummVMPackReadStream f(pf);
f.seek(0);
return decodeImageStream<DECODER>(f, pal);
}
BITMAP *load_bmp(const char *filename, color *pal) {
return decodeImage<Image::BitmapDecoder>(filename, pal);
}
BITMAP *load_lbm(const char *filename, color *pal) {
return decodeImage<Image::IFFDecoder>(filename, pal);
}
BITMAP *load_pcx(const char *filename, color *pal) {
return decodeImage<Image::PCXDecoder>(filename, pal);
}
BITMAP *load_tga(const char *filename, color *pal) {
return decodeImage<Image::TGADecoder>(filename, pal);
}
BITMAP *load_bitmap(const char *filename, color *pal) {
Common::String fname(filename);
if (fname.hasSuffixIgnoreCase(".bmp"))
return load_bmp(filename, pal);
else if (fname.hasSuffixIgnoreCase(".lbm"))
return load_lbm(filename, pal);
else if (fname.hasSuffixIgnoreCase(".pcx"))
return load_pcx(filename, pal);
else if (fname.hasSuffixIgnoreCase(".tga"))
return load_tga(filename, pal);
else
error("Unknown image file - %s", filename);
}
BITMAP *load_bitmap(PACKFILE *pf, color *pal) {
BITMAP *result;
if ((result = decodeImage<Image::BitmapDecoder>(pf, pal)) != nullptr)
return result;
if ((result = decodeImage<Image::IFFDecoder>(pf, pal)) != nullptr)
return result;
if ((result = decodeImage<Image::PCXDecoder>(pf, pal)) != nullptr)
return result;
if ((result = decodeImage<Image::TGADecoder>(pf, pal)) != nullptr)
return result;
error("Unknown image file");
}
bool save_bitmap(Common::WriteStream &out, BITMAP *bmp, const RGB *pal) {
const Graphics::ManagedSurface &src = bmp->getSurface();
if (bmp->format.isCLUT8() && pal) {
byte palette[256 * 3];
for (int c = 0, i = 0; c < 256; ++c, i += 3) {
palette[i] = VGA_COLOR_TRANS(pal[c].r);
palette[i + 1] = VGA_COLOR_TRANS(pal[c].g);
palette[i + 2] = VGA_COLOR_TRANS(pal[c].b);
}
return Image::writeBMP(out, src, palette);
} else {
return Image::writeBMP(out, src);
}
}
} // namespace AGS

View File

@@ -0,0 +1,42 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_GFX_IMAGE_H
#define AGS_SHARED_GFX_IMAGE_H
#include "ags/lib/allegro.h"
namespace AGS3 {
struct PACKFILE;
extern BITMAP *load_bitmap(PACKFILE *pf, color *pal);
extern BITMAP *load_bitmap(const char *filename, color *pal);
extern BITMAP *load_bmp(const char *filename, color *pal);
extern BITMAP *load_lbm(const char *filename, color *pal);
extern BITMAP *load_pcx(const char *filename, color *pal);
extern BITMAP *load_tga(const char *filename, color *pal);
extern bool save_bitmap(Common::WriteStream &out, BITMAP *bmp, const RGB *pal);
} // namespace AGS3
#endif

View File

@@ -0,0 +1,483 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/ac/sprite_cache.h"
#include "ags/shared/ac/game_struct_defines.h"
#include "ags/shared/font/fonts.h"
#include "ags/shared/gui/gui_button.h"
#include "ags/shared/gui/gui_main.h" // TODO: extract helper functions
#include "ags/shared/util/stream.h"
#include "ags/shared/util/string_utils.h"
#include "ags/globals.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
FrameAlignment ConvertLegacyButtonAlignment(LegacyButtonAlignment align) {
switch (align) {
case kLegacyButtonAlign_TopCenter:
return kAlignTopCenter;
case kLegacyButtonAlign_TopLeft:
return kAlignTopLeft;
case kLegacyButtonAlign_TopRight:
return kAlignTopRight;
case kLegacyButtonAlign_CenterLeft:
return kAlignMiddleLeft;
case kLegacyButtonAlign_Centered:
return kAlignMiddleCenter;
case kLegacyButtonAlign_CenterRight:
return kAlignMiddleRight;
case kLegacyButtonAlign_BottomLeft:
return kAlignBottomLeft;
case kLegacyButtonAlign_BottomCenter:
return kAlignBottomCenter;
case kLegacyButtonAlign_BottomRight:
return kAlignBottomRight;
default:
break;
}
return kAlignNone;
}
GUIButton::GUIButton() {
_image = -1;
_mouseOverImage = -1;
_pushedImage = -1;
_currentImage = -1;
Font = 0;
TextColor = 0;
TextAlignment = kAlignTopCenter;
ClickAction[kGUIClickLeft] = kGUIAction_RunScript;
ClickAction[kGUIClickRight] = kGUIAction_RunScript;
ClickData[kGUIClickLeft] = 0;
ClickData[kGUIClickRight] = 0;
IsPushed = false;
IsMouseOver = false;
_placeholder = kButtonPlace_None;
_unnamed = true;
_scEventCount = 1;
_scEventNames[0] = "Click";
_scEventArgs[0] = "GUIControl *control, MouseButton button";
}
bool GUIButton::HasAlphaChannel() const {
return ((_currentImage > 0) && is_sprite_alpha(_currentImage)) ||
(!_unnamed && is_font_antialiased(Font));
}
int32_t GUIButton::GetCurrentImage() const {
return _currentImage;
}
int32_t GUIButton::GetNormalImage() const {
return _image;
}
int32_t GUIButton::GetMouseOverImage() const {
return _mouseOverImage;
}
int32_t GUIButton::GetPushedImage() const {
return _pushedImage;
}
GUIButtonPlaceholder GUIButton::GetPlaceholder() const {
return _placeholder;
}
const String &GUIButton::GetText() const {
return _text;
}
bool GUIButton::IsImageButton() const {
return _image > 0;
}
bool GUIButton::IsClippingImage() const {
return (Flags & kGUICtrl_Clip) != 0;
}
Rect GUIButton::CalcGraphicRect(bool clipped) {
if (clipped)
return RectWH(0, 0, _width, _height);
// TODO: need to find a way to cache image and text position, or there'll be some repetition
Rect rc = RectWH(0, 0, _width, _height);
if (IsImageButton()) {
if (IsClippingImage())
return rc;
// Main button graphic
if (_currentImage >= 0 && _GP(spriteset).DoesSpriteExist(_currentImage))
rc = SumRects(rc, RectWH(0, 0, get_adjusted_spritewidth(_currentImage), get_adjusted_spriteheight(_currentImage)));
// Optionally merge with the inventory pic
if (_placeholder != kButtonPlace_None && _G(gui_inv_pic) >= 0) {
Size inv_sz = Size(get_adjusted_spritewidth(_G(gui_inv_pic)),
get_adjusted_spriteheight(_G(gui_inv_pic)));
GUIButtonPlaceholder place = _placeholder;
if (place == kButtonPlace_InvItemAuto) {
place = ((inv_sz.Width > _width - 6) || (inv_sz.Height > _height - 6)) ?
kButtonPlace_InvItemStretch : kButtonPlace_InvItemCenter;
}
Rect inv_rc = (place == kButtonPlace_InvItemStretch) ?
RectWH(0 + 3, 0 + 3, _width - 6, _height - 6) :
RectWH(0 + _width / 2 - inv_sz.Width / 2,
0 + _height / 2 - inv_sz.Height / 2,
inv_sz.Width, inv_sz.Height);
rc = SumRects(rc, inv_rc);
}
}
// Optionally merge with the button text
if (!IsImageButton() || ((_placeholder == kButtonPlace_None) && !_unnamed)) {
PrepareTextToDraw();
Rect frame = RectWH(0 + 2, 0 + 2, _width - 4, _height - 4);
if (IsPushed && IsMouseOver) {
frame.Left++;
frame.Top++;
}
rc = SumRects(rc, GUI::CalcTextGraphicalRect(_textToDraw.GetCStr(), Font, frame, TextAlignment));
}
return rc;
}
void GUIButton::Draw(Bitmap *ds, int x, int y) {
bool draw_disabled = !IsGUIEnabled(this);
// if it's "Unchanged when disabled" or "GUI Off", don't grey out
if ((GUI::Options.DisabledStyle == kGuiDis_Unchanged) ||
(GUI::Options.DisabledStyle == kGuiDis_Off)) {
draw_disabled = false;
}
// TODO: should only change properties in reaction to particular events
if (_currentImage <= 0 || draw_disabled)
_currentImage = _image;
if (draw_disabled && (GUI::Options.DisabledStyle == kGuiDis_Blackout))
// buttons off when disabled - no point carrying on
return;
if (IsImageButton())
DrawImageButton(ds, x, y, draw_disabled);
// CHECKME: why don't draw frame if no Text? this will make button completely invisible!
else if (!_text.IsEmpty())
DrawTextButton(ds, x, y, draw_disabled);
}
void GUIButton::SetClipImage(bool on) {
if (on != ((Flags & kGUICtrl_Clip) != 0))
MarkChanged();
if (on)
Flags |= kGUICtrl_Clip;
else
Flags &= ~kGUICtrl_Clip;
}
void GUIButton::SetCurrentImage(int32_t image) {
if (_currentImage == image)
return;
_currentImage = image;
MarkChanged();
}
void GUIButton::SetMouseOverImage(int32_t image) {
if (_mouseOverImage == image)
return;
_mouseOverImage = image;
UpdateCurrentImage();
}
void GUIButton::SetNormalImage(int32_t image) {
if (_image == image)
return;
_image = image;
UpdateCurrentImage();
}
void GUIButton::SetPushedImage(int32_t image) {
if (_pushedImage == image)
return;
_pushedImage = image;
UpdateCurrentImage();
}
void GUIButton::SetImages(int32_t normal, int32_t over, int32_t pushed) {
_image = normal;
_mouseOverImage = over;
_pushedImage = pushed;
UpdateCurrentImage();
}
void GUIButton::SetText(const String &text) {
if (_text == text)
return;
_text = text;
// Active inventory item placeholders
if (_text.CompareNoCase("(INV)") == 0)
// Stretch to fit button
_placeholder = kButtonPlace_InvItemStretch;
else if (_text.CompareNoCase("(INVNS)") == 0)
// Draw at actual size
_placeholder = kButtonPlace_InvItemCenter;
else if (_text.CompareNoCase("(INVSHR)") == 0)
// Stretch if too big, actual size if not
_placeholder = kButtonPlace_InvItemAuto;
else
_placeholder = kButtonPlace_None;
// TODO: find a way to remove this bogus limitation ("New Button" is a valid Text too)
_unnamed = _text.IsEmpty() || _text.Compare("New Button") == 0;
MarkChanged();
}
bool GUIButton::OnMouseDown() {
if (!IsImageButton())
MarkChanged();
IsPushed = true;
UpdateCurrentImage();
return false;
}
void GUIButton::OnMouseEnter() {
if (IsPushed && !IsImageButton())
MarkChanged();
IsMouseOver = true;
UpdateCurrentImage();
}
void GUIButton::OnMouseLeave() {
if (IsPushed && !IsImageButton())
MarkChanged();
IsMouseOver = false;
UpdateCurrentImage();
}
void GUIButton::OnMouseUp() {
if (IsMouseOver) {
if (IsGUIEnabled(this) && IsClickable())
IsActivated = true;
}
if (IsPushed && !IsImageButton())
MarkChanged();
IsPushed = false;
UpdateCurrentImage();
}
void GUIButton::UpdateCurrentImage() {
int new_image = _currentImage;
if (IsPushed && (_pushedImage > 0)) {
new_image = _pushedImage;
} else if (IsMouseOver && (_mouseOverImage > 0)) {
new_image = _mouseOverImage;
} else {
new_image = _image;
}
SetCurrentImage(new_image);
}
void GUIButton::WriteToFile(Stream *out) const {
GUIObject::WriteToFile(out);
out->WriteInt32(_image);
out->WriteInt32(_mouseOverImage);
out->WriteInt32(_pushedImage);
out->WriteInt32(Font);
out->WriteInt32(TextColor);
out->WriteInt32(ClickAction[kGUIClickLeft]);
out->WriteInt32(ClickAction[kGUIClickRight]);
out->WriteInt32(ClickData[kGUIClickLeft]);
out->WriteInt32(ClickData[kGUIClickRight]);
StrUtil::WriteString(_text, out);
out->WriteInt32(TextAlignment);
}
void GUIButton::ReadFromFile(Stream *in, GuiVersion gui_version) {
GUIObject::ReadFromFile(in, gui_version);
_image = in->ReadInt32();
_mouseOverImage = in->ReadInt32();
_pushedImage = in->ReadInt32();
if (gui_version < kGuiVersion_350) { // NOTE: reading into actual variables only for old savegame support
_currentImage = in->ReadInt32();
IsPushed = in->ReadInt32() != 0;
IsMouseOver = in->ReadInt32() != 0;
}
Font = in->ReadInt32();
TextColor = in->ReadInt32();
ClickAction[kGUIClickLeft] = (GUIClickAction)in->ReadInt32();
ClickAction[kGUIClickRight] = (GUIClickAction)in->ReadInt32();
ClickData[kGUIClickLeft] = in->ReadInt32();
ClickData[kGUIClickRight] = in->ReadInt32();
if (gui_version < kGuiVersion_350)
SetText(String::FromStreamCount(in, GUIBUTTON_LEGACY_TEXTLENGTH));
else
SetText(StrUtil::ReadString(in));
if (gui_version >= kGuiVersion_272a) {
if (gui_version < kGuiVersion_350) {
TextAlignment = ConvertLegacyButtonAlignment((LegacyButtonAlignment)in->ReadInt32());
in->ReadInt32(); // reserved1
} else {
TextAlignment = (FrameAlignment)in->ReadInt32();
}
} else {
TextAlignment = kAlignTopCenter;
}
if (TextColor == 0)
TextColor = 16;
_currentImage = _image;
}
void GUIButton::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) {
GUIObject::ReadFromSavegame(in, svg_ver);
// Properties
_image = in->ReadInt32();
_mouseOverImage = in->ReadInt32();
_pushedImage = in->ReadInt32();
Font = in->ReadInt32();
TextColor = in->ReadInt32();
SetText(StrUtil::ReadString(in));
if (svg_ver >= kGuiSvgVersion_350)
TextAlignment = (FrameAlignment)in->ReadInt32();
// Dynamic state
_currentImage = in->ReadInt32();
// Update current state after reading
IsPushed = false;
IsMouseOver = false;
}
void GUIButton::WriteToSavegame(Stream *out) const {
// Properties
GUIObject::WriteToSavegame(out);
out->WriteInt32(_image);
out->WriteInt32(_mouseOverImage);
out->WriteInt32(_pushedImage);
out->WriteInt32(Font);
out->WriteInt32(TextColor);
StrUtil::WriteString(GetText(), out);
out->WriteInt32(TextAlignment);
// Dynamic state
out->WriteInt32(_currentImage);
}
void GUIButton::DrawImageButton(Bitmap *ds, int x, int y, bool draw_disabled) {
assert(_currentImage >= 0);
// NOTE: the CLIP flag only clips the image, not the text
if (IsClippingImage() && !GUI::Options.ClipControls)
ds->SetClip(RectWH(x, y, _width, _height));
if (_GP(spriteset).DoesSpriteExist(_currentImage))
draw_gui_sprite(ds, _currentImage, x, y, true);
// Draw active inventory item
if (_placeholder != kButtonPlace_None && _G(gui_inv_pic) >= 0) {
Size inv_sz = Size(get_adjusted_spritewidth(_G(gui_inv_pic)), get_adjusted_spriteheight(_G(gui_inv_pic)));
GUIButtonPlaceholder place = _placeholder;
if (place == kButtonPlace_InvItemAuto) {
place = ((inv_sz.Width > _width - 6) || (inv_sz.Height > _height - 6)) ?
kButtonPlace_InvItemStretch : kButtonPlace_InvItemCenter;
}
if (place == kButtonPlace_InvItemStretch) {
ds->StretchBlt(_GP(spriteset)[_G(gui_inv_pic)], RectWH(x + 3, y + 3, _width - 6, _height - 6),
kBitmap_Transparency);
} else {
draw_gui_sprite(ds, _G(gui_inv_pic),
x + _width / 2 - inv_sz.Width / 2,
y + _height / 2 - inv_sz.Height / 2,
true);
}
}
if ((draw_disabled) && (GUI::Options.DisabledStyle == kGuiDis_Greyout)) {
// darken the button when disabled
const Size sz = _GP(spriteset).GetSpriteResolution(_currentImage);
GUI::DrawDisabledEffect(ds, RectWH(x, y, sz.Width, sz.Height));
}
// Don't print Text of (INV) (INVSHR) (INVNS)
if ((_placeholder == kButtonPlace_None) && !_unnamed)
DrawText(ds, x, y, draw_disabled);
if (IsClippingImage() && !GUI::Options.ClipControls)
ds->ResetClip();
}
void GUIButton::DrawText(Bitmap *ds, int x, int y, bool draw_disabled) {
// TODO: need to find a way to cache Text prior to drawing;
// but that will require to update all gui controls when translation is changed in game
PrepareTextToDraw();
Rect frame = RectWH(x + 2, y + 2, _width - 4, _height - 4);
if (IsPushed && IsMouseOver) {
// move the Text a bit while pushed
frame.Left++;
frame.Top++;
}
color_t text_color = ds->GetCompatibleColor(TextColor);
if (draw_disabled)
text_color = ds->GetCompatibleColor(8);
GUI::DrawTextAligned(ds, _textToDraw.GetCStr(), Font, text_color, frame, TextAlignment);
}
void GUIButton::DrawTextButton(Bitmap *ds, int x, int y, bool draw_disabled) {
color_t draw_color = ds->GetCompatibleColor(7);
ds->FillRect(Rect(x, y, x + _width - 1, y + _height - 1), draw_color);
if (Flags & kGUICtrl_Default) {
draw_color = ds->GetCompatibleColor(16);
ds->DrawRect(Rect(x - 1, y - 1, x + _width, y + _height), draw_color);
}
// TODO: use color constants instead of literal numbers
if (!draw_disabled && IsMouseOver && IsPushed)
draw_color = ds->GetCompatibleColor(15);
else
draw_color = ds->GetCompatibleColor(8);
ds->DrawLine(Line(x, y + _height - 1, x + _width - 1, y + _height - 1), draw_color);
ds->DrawLine(Line(x + _width - 1, y, x + _width - 1, y + _height - 1), draw_color);
if (draw_disabled || (IsMouseOver && IsPushed))
draw_color = ds->GetCompatibleColor(8);
else
draw_color = ds->GetCompatibleColor(15);
ds->DrawLine(Line(x, y, x + _width - 1, y), draw_color);
ds->DrawLine(Line(x, y, x, y + _height - 1), draw_color);
DrawText(ds, x, y, draw_disabled);
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,150 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_GUI_GUI_BUTTON_H
#define AGS_SHARED_GUI_GUI_BUTTON_H
#include "common/std/vector.h"
#include "ags/engine/ac/button.h"
#include "ags/shared/gui/gui_object.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
#define GUIBUTTON_LEGACY_TEXTLENGTH 50
namespace AGS {
namespace Shared {
enum GUIClickMouseButton {
kGUIClickLeft = 0,
kGUIClickRight = 1,
kNumGUIClicks
};
enum GUIClickAction {
kGUIAction_None = 0,
kGUIAction_SetMode = 1,
kGUIAction_RunScript = 2,
};
enum LegacyButtonAlignment {
kLegacyButtonAlign_TopCenter = 0,
kLegacyButtonAlign_TopLeft = 1,
kLegacyButtonAlign_TopRight = 2,
kLegacyButtonAlign_CenterLeft = 3,
kLegacyButtonAlign_Centered = 4,
kLegacyButtonAlign_CenterRight = 5,
kLegacyButtonAlign_BottomLeft = 6,
kLegacyButtonAlign_BottomCenter = 7,
kLegacyButtonAlign_BottomRight = 8,
};
// Defines button placeholder mode; the mode is set
// depending on special tags found in button text
enum GUIButtonPlaceholder {
kButtonPlace_None,
kButtonPlace_InvItemStretch,
kButtonPlace_InvItemCenter,
kButtonPlace_InvItemAuto
};
class GUIButton : public GUIObject {
public:
GUIButton();
bool HasAlphaChannel() const override;
int32_t GetCurrentImage() const;
int32_t GetNormalImage() const;
int32_t GetMouseOverImage() const;
int32_t GetPushedImage() const;
GUIButtonPlaceholder GetPlaceholder() const;
const String &GetText() const;
bool IsImageButton() const;
bool IsClippingImage() const;
// Operations
Rect CalcGraphicRect(bool clipped) override;
void Draw(Bitmap *ds, int x = 0, int y = 0) override;
void SetClipImage(bool on);
void SetCurrentImage(int32_t image);
void SetMouseOverImage(int32_t image);
void SetNormalImage(int32_t image);
void SetPushedImage(int32_t image);
void SetImages(int32_t normal, int32_t over, int32_t pushed);
void SetText(const String &text);
// Events
bool OnMouseDown() override;
void OnMouseEnter() override;
void OnMouseLeave() override;
void OnMouseUp() override;
// Serialization
void ReadFromFile(Stream *in, GuiVersion gui_version) override;
void WriteToFile(Stream *out) const override;
void ReadFromSavegame(Shared::Stream *in, GuiSvgVersion svg_ver) override;
void WriteToSavegame(Shared::Stream *out) const override;
// TODO: these members are currently public; hide them later
public:
int32_t Font;
color_t TextColor;
FrameAlignment TextAlignment;
// Click actions for left and right mouse buttons
// NOTE: only left click is currently in use
GUIClickAction ClickAction[kNumGUIClicks];
int32_t ClickData[kNumGUIClicks];
bool IsPushed;
bool IsMouseOver;
private:
void DrawImageButton(Bitmap *ds, int x, int y, bool draw_disabled);
void DrawText(Bitmap *ds, int x, int y, bool draw_disabled);
void DrawTextButton(Bitmap *ds, int x, int y, bool draw_disabled);
void PrepareTextToDraw();
// Update current image depending on the button's state
void UpdateCurrentImage();
int32_t _image;
int32_t _mouseOverImage;
int32_t _pushedImage;
// Active displayed image
int32_t _currentImage;
// Text property set by user
String _text;
// type of content placeholder, if any
GUIButtonPlaceholder _placeholder;
// A flag indicating unnamed button; this is a convenience trick:
// buttons are created named "New Button" in the editor, and users
// often do not clear text when they want a graphic button.
bool _unnamed;
// Prepared text buffer/cache
String _textToDraw;
};
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,216 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_GUI_GUI_DEFINES_H
#define AGS_SHARED_GUI_GUI_DEFINES_H
namespace AGS3 {
#define GUIMAGIC 0xcafebeef
#define MAX_GUIOBJ_EVENTS 10
#define TEXTWINDOW_PADDING_DEFAULT 3
// TODO: find out more about gui version history
//=============================================================================
// GUI Version history
//-----------------------------------------------------------------------------
//
// 2.1.4..... (100): Introduced Slider gui control. Gui count is now serialized
// in game file.
// 2.2.2..... (101): Introduced TextBox gui control.
// 2.3.0..... (102): Introduced ListBox gui control.
// 2.6.0..... (105): GUI custom Z-order support.
// 2.7.0..... (110): Added GUI OnClick handler.
// 2.7.2.???? (111): Added text alignment property to buttons.
// 2.7.2.???? (112): Added text alignment property to list boxes.
// 2.7.2.???? (113): Increased permitted length of GUI label text from 200 to
// 2048 characters.
// 2.7.2.???? (114): Obsoleted savegameindex[] array, and added
// ListBox.SaveGameSlots[] array instead.
// 2.7.2.???? (115): Added GUI Control z-order support.
//
// 3.3.0.1132 (116): Added kGUICtrl_Translated flag.
// 3.3.1.???? (117): Added padding variable for text window GUIs.
// 3.4.0 (118): Removed GUI limits
// 3.5.0 (119): Game data contains GUI properties that previously
// could be set only at runtime.
// Since then format value is defined as AGS version represented as NN,NN,NN,NN
//=============================================================================
enum GuiVersion {
// TODO: find out all corresponding engine version numbers
kGuiVersion_Initial = 0,
kGuiVersion_214 = 100,
kGuiVersion_222 = 101,
kGuiVersion_230 = 102,
kGuiVersion_unkn_103 = 103,
kGuiVersion_unkn_104 = 104,
kGuiVersion_260 = 105,
kGuiVersion_unkn_106 = 106,
kGuiVersion_unkn_107 = 107,
kGuiVersion_unkn_108 = 108,
kGuiVersion_unkn_109 = 109,
kGuiVersion_270 = 110,
kGuiVersion_272a = 111,
kGuiVersion_272b = 112,
kGuiVersion_272c = 113,
kGuiVersion_272d = 114,
kGuiVersion_272e = 115,
kGuiVersion_330 = 116,
kGuiVersion_331 = 117,
kGuiVersion_340 = 118,
kGuiVersion_350 = 119,
kGuiVersion_Current = kGuiVersion_350,
};
namespace AGS {
namespace Shared {
// GUIMain's style and behavior flags
enum GUIMainFlags {
kGUIMain_Clickable = 0x0001,
kGUIMain_TextWindow = 0x0002,
kGUIMain_Visible = 0x0004,
kGUIMain_Concealed = 0x0008,
// NOTE: currently default state is Visible to keep this backwards compatible;
// check later if this is still a wanted behavior
kGUIMain_DefFlags = kGUIMain_Clickable | kGUIMain_Visible,
// flags that had inverse meaning in old formats
kGUIMain_OldFmtXorMask = kGUIMain_Clickable
};
// GUIMain's legacy flags, now converted to GUIMainFlags on load
enum GUIMainLegacyFlags {
kGUIMain_LegacyTextWindow = 5
};
// GUIMain's style of getting displayed on screen
enum GUIPopupStyle {
// Normal GUI
kGUIPopupNormal = 0,
// Shown when the mouse cursor moves to the top of the screen
kGUIPopupMouseY = 1,
// Same as Normal, but pauses the game when shown
kGUIPopupModal = 2,
// Same as Normal, but is not removed when interface is off
kGUIPopupNoAutoRemove = 3,
// (legacy option) Normal GUI, initially off
// converts to kGUIPopupNormal with Visible = false
kGUIPopupLegacyNormalOff = 4
};
// The type of GUIControl
enum GUIControlType {
kGUIControlUndefined = -1,
kGUIButton = 1,
kGUILabel = 2,
kGUIInvWindow = 3,
kGUISlider = 4,
kGUITextBox = 5,
kGUIListBox = 6
};
// GUIControl general style and behavior flags
enum GUIControlFlags {
kGUICtrl_Default = 0x0001, // only button
kGUICtrl_Cancel = 0x0002, // unused
kGUICtrl_Enabled = 0x0004,
kGUICtrl_TabStop = 0x0008, // unused
kGUICtrl_Visible = 0x0010,
kGUICtrl_Clip = 0x0020, // only button
kGUICtrl_Clickable = 0x0040,
kGUICtrl_Translated = 0x0080, // 3.3.0.1132
kGUICtrl_Deleted = 0x8000, // unused (probably remains from the old editor?)
kGUICtrl_DefFlags = kGUICtrl_Enabled | kGUICtrl_Visible | kGUICtrl_Clickable | kGUICtrl_Translated,
// flags that had inverse meaning in old formats
kGUICtrl_OldFmtXorMask = kGUICtrl_Enabled | kGUICtrl_Visible | kGUICtrl_Clickable
};
// Label macro flags, define which macros are present in the Label's Text
enum GUILabelMacro {
kLabelMacro_None = 0,
kLabelMacro_Gamename = 0x01,
kLabelMacro_Overhotspot = 0x02,
kLabelMacro_Score = 0x04,
kLabelMacro_ScoreText = 0x08,
kLabelMacro_TotalScore = 0x10,
kLabelMacro_AllScore = kLabelMacro_Score | kLabelMacro_ScoreText,
kLabelMacro_All = 0xFFFF
};
// GUIListBox style and behavior flags
enum GUIListBoxFlags {
kListBox_ShowBorder = 0x01,
kListBox_ShowArrows = 0x02,
kListBox_SvgIndex = 0x04,
kListBox_DefFlags = kListBox_ShowBorder | kListBox_ShowArrows,
// flags that had inverse meaning in old formats
kListBox_OldFmtXorMask = kListBox_ShowBorder | kListBox_ShowArrows
};
// GUITextBox style and behavior flags
enum GUITextBoxFlags {
kTextBox_ShowBorder = 0x0001,
kTextBox_DefFlags = kTextBox_ShowBorder,
// flags that had inverse meaning in old formats
kTextBox_OldFmtXorMask = kTextBox_ShowBorder
};
// Savegame data format
// TODO: move to the engine code
enum GuiSvgVersion {
kGuiSvgVersion_Initial = 0,
kGuiSvgVersion_350,
kGuiSvgVersion_36020,
kGuiSvgVersion_36023,
kGuiSvgVersion_36025
};
// Style of GUI drawing in disabled state
enum GuiDisableStyle {
kGuiDis_Undefined = -1, // this is for marking not-disabled state
kGuiDis_Greyout = 0, // paint "gisabled" effect over controls
kGuiDis_Blackout = 1, // invisible controls (but guis are shown
kGuiDis_Unchanged = 2, // no change, display normally
kGuiDis_Off = 3 // fully invisible guis
};
// Global GUI options
struct GuiOptions {
// Clip GUI control's contents to the control's rectangle
bool ClipControls = true;
// How the GUI controls are drawn when the interface is disabled
GuiDisableStyle DisabledStyle = kGuiDis_Unchanged;
// Whether to graphically outline GUI controls
bool OutlineControls = false;
};
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,128 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/ac/game_version.h"
#include "ags/shared/gui/gui_inv.h"
#include "ags/shared/gui/gui_main.h"
#include "ags/shared/util/stream.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
GUIInvWindow::GUIInvWindow() {
IsMouseOver = false;
CharId = -1;
ItemWidth = 40;
ItemHeight = 22;
ColCount = 0;
RowCount = 0;
TopItem = 0;
CalculateNumCells();
_scEventCount = 0;
}
void GUIInvWindow::OnMouseEnter() {
IsMouseOver = true;
}
void GUIInvWindow::OnMouseLeave() {
IsMouseOver = false;
}
void GUIInvWindow::OnMouseUp() {
if (IsMouseOver)
IsActivated = true;
}
void GUIInvWindow::OnResized() {
CalculateNumCells();
MarkChanged();
}
void GUIInvWindow::WriteToFile(Stream *out) const {
GUIObject::WriteToFile(out);
out->WriteInt32(CharId);
out->WriteInt32(ItemWidth);
out->WriteInt32(ItemHeight);
}
void GUIInvWindow::ReadFromFile(Stream *in, GuiVersion gui_version) {
GUIObject::ReadFromFile(in, gui_version);
if (gui_version >= kGuiVersion_unkn_109) {
CharId = in->ReadInt32();
ItemWidth = in->ReadInt32();
ItemHeight = in->ReadInt32();
if (gui_version < kGuiVersion_350) { // NOTE: reading into actual variables only for old savegame support
TopItem = in->ReadInt32();
}
} else {
CharId = -1;
ItemWidth = 40;
ItemHeight = 22;
TopItem = 0;
}
if (_G(loaded_game_file_version) >= kGameVersion_270) {
// ensure that some items are visible
if (ItemWidth > _width)
ItemWidth = _width;
if (ItemHeight > _height)
ItemHeight = _height;
}
CalculateNumCells();
}
void GUIInvWindow::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) {
GUIObject::ReadFromSavegame(in, svg_ver);
ItemWidth = in->ReadInt32();
ItemHeight = in->ReadInt32();
CharId = in->ReadInt32();
TopItem = in->ReadInt32();
CalculateNumCells();
}
void GUIInvWindow::WriteToSavegame(Stream *out) const {
GUIObject::WriteToSavegame(out);
out->WriteInt32(ItemWidth);
out->WriteInt32(ItemHeight);
out->WriteInt32(CharId);
out->WriteInt32(TopItem);
}
void GUIInvWindow::CalculateNumCells() {
if (ItemWidth <= 0 || ItemHeight <= 0) {
ColCount = 0;
RowCount = 0;
} else if (_G(loaded_game_file_version) >= kGameVersion_270) {
ColCount = _width / data_to_game_coord(ItemWidth);
RowCount = _height / data_to_game_coord(ItemHeight);
} else {
ColCount = floor((float)_width / (float)data_to_game_coord(ItemWidth) + 0.5f);
RowCount = floor((float)_height / (float)data_to_game_coord(ItemHeight) + 0.5f);
}
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,74 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_GUI_GUI_INV_H
#define AGS_SHARED_GUI_GUI_INV_H
#include "common/std/vector.h"
#include "ags/shared/gui/gui_object.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class GUIInvWindow : public GUIObject {
public:
GUIInvWindow();
bool HasAlphaChannel() const override;
// This function has distinct implementations in Engine and Editor
int GetCharacterId() const;
// Operations
// This function has distinct implementations in Engine and Editor
void Draw(Bitmap *ds, int x = 0, int y = 0) override;
// Events
void OnMouseEnter() override;
void OnMouseLeave() override;
void OnMouseUp() override;
void OnResized() override;
// Serialization
void ReadFromFile(Stream *in, GuiVersion gui_version) override;
void WriteToFile(Stream *out) const override;
void ReadFromSavegame(Shared::Stream *in, GuiSvgVersion svg_ver) override;
void WriteToSavegame(Shared::Stream *out) const override;
// TODO: these members are currently public; hide them later
public:
bool IsMouseOver;
int32_t CharId; // whose inventory (-1 = current player)
int32_t ItemWidth;
int32_t ItemHeight;
int32_t ColCount;
int32_t RowCount;
int32_t TopItem;
private:
void CalculateNumCells();
};
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,172 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/std/algorithm.h"
#include "ags/shared/ac/game_version.h"
#include "ags/shared/font/fonts.h"
#include "ags/shared/gui/gui_label.h"
#include "ags/shared/gui/gui_main.h"
#include "ags/shared/util/stream.h"
#include "ags/shared/util/string_utils.h"
#define GUILABEL_TEXTLENGTH_PRE272 200
namespace AGS3 {
namespace AGS {
namespace Shared {
GUILabel::GUILabel() {
Font = 0;
TextColor = 0;
TextAlignment = kHAlignLeft;
_scEventCount = 0;
}
bool GUILabel::HasAlphaChannel() const {
return is_font_antialiased(Font);
}
String GUILabel::GetText() const {
return Text;
}
GUILabelMacro GUILabel::GetTextMacros() const {
return _textMacro;
}
Rect GUILabel::CalcGraphicRect(bool clipped) {
if (clipped)
return RectWH(0, 0, _width, _height);
// TODO: need to find a way to text position, or there'll be some repetition
// have to precache text and size on some events:
// - translation change
// - macro value change (score, overhotspot etc)
Rect rc = RectWH(0, 0, _width, _height);
if (PrepareTextToDraw() == 0)
return rc;
const int linespacing = // Older engine labels used (font height + 1) as linespacing for some reason
((_G(loaded_game_file_version) < kGameVersion_360) && (get_font_flags(Font) & FFLG_DEFLINESPACING)) ?
(get_font_height(Font) + 1) :
get_font_linespacing(Font);
// < 2.72 labels did not limit vertical size of text
const bool limit_by_label_frame = _G(loaded_game_file_version) >= kGameVersion_272;
int at_y = 0;
Line max_line;
for (size_t i = 0;
i < _GP(Lines).Count() && (!limit_by_label_frame || at_y <= _height);
++i, at_y += linespacing) {
Line lpos = GUI::CalcTextPositionHor(_GP(Lines)[i].GetCStr(), Font, 0, 0 + _width - 1, at_y,
(FrameAlignment)TextAlignment);
max_line.X2 = MAX(max_line.X2, lpos.X2);
}
// Include font fixes for the first and last text line,
// in case graphical height is different, and there's a VerticalOffset
Line vextent = GUI::CalcFontGraphicalVExtent(Font);
Rect text_rc = RectWH(0, vextent.Y1, max_line.X2 - max_line.X1 + 1, at_y - linespacing + (vextent.Y2 - vextent.Y1));
return SumRects(rc, text_rc);
}
void GUILabel::Draw(Bitmap *ds, int x, int y) {
// TODO: need to find a way to cache text prior to drawing;
// but that will require to update all gui controls when translation is changed in game
if (PrepareTextToDraw() == 0)
return;
color_t text_color = ds->GetCompatibleColor(TextColor);
const int linespacing = // Older engine labels used (font height + 1) as linespacing for some reason
((_G(loaded_game_file_version) < kGameVersion_360) && (get_font_flags(Font) & FFLG_DEFLINESPACING)) ?
(get_font_height(Font) + 1) :
get_font_linespacing(Font);
// < 2.72 labels did not limit vertical size of text
const bool limit_by_label_frame = _G(loaded_game_file_version) >= kGameVersion_272;
int at_y = y;
for (size_t i = 0;
i < _GP(Lines).Count() && (!limit_by_label_frame || at_y <= y + _height);
++i, at_y += linespacing) {
GUI::DrawTextAlignedHor(ds, _GP(Lines)[i].GetCStr(), Font, text_color, x, x + _width - 1, at_y,
(FrameAlignment)TextAlignment);
}
}
void GUILabel::SetText(const String &text) {
if (text == Text)
return;
Text = text;
// Check for macros within text
_textMacro = GUI::FindLabelMacros(Text);
MarkChanged();
}
// TODO: replace string serialization with StrUtil::ReadString and WriteString
// methods in the future, to keep this organized.
void GUILabel::WriteToFile(Stream *out) const {
GUIObject::WriteToFile(out);
StrUtil::WriteString(Text, out);
out->WriteInt32(Font);
out->WriteInt32(TextColor);
out->WriteInt32(TextAlignment);
}
void GUILabel::ReadFromFile(Stream *in, GuiVersion gui_version) {
GUIObject::ReadFromFile(in, gui_version);
if (gui_version < kGuiVersion_272c)
Text.ReadCount(in, GUILABEL_TEXTLENGTH_PRE272);
else
Text = StrUtil::ReadString(in);
Font = in->ReadInt32();
TextColor = in->ReadInt32();
if (gui_version < kGuiVersion_350)
TextAlignment = ConvertLegacyGUIAlignment((LegacyGUIAlignment)in->ReadInt32());
else
TextAlignment = (HorAlignment)in->ReadInt32();
if (TextColor == 0)
TextColor = 16;
_textMacro = GUI::FindLabelMacros(Text);
}
void GUILabel::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) {
GUIObject::ReadFromSavegame(in, svg_ver);
Font = in->ReadInt32();
TextColor = in->ReadInt32();
Text = StrUtil::ReadString(in);
if (svg_ver >= kGuiSvgVersion_350)
TextAlignment = (HorAlignment)in->ReadInt32();
_textMacro = GUI::FindLabelMacros(Text);
}
void GUILabel::WriteToSavegame(Stream *out) const {
GUIObject::WriteToSavegame(out);
out->WriteInt32(Font);
out->WriteInt32(TextColor);
StrUtil::WriteString(Text, out);
out->WriteInt32(TextAlignment);
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,78 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_GUI_GUI_LABEL_H
#define AGS_SHARED_GUI_GUI_LABEL_H
#include "common/std/vector.h"
#include "ags/shared/gui/gui_object.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class GUILabel : public GUIObject {
public:
GUILabel();
bool HasAlphaChannel() const override;
// Gets label's text property in original set form (with macros etc)
String GetText() const;
// Gets which macro are contained within label's text
GUILabelMacro GetTextMacros() const;
// Operations
Rect CalcGraphicRect(bool clipped) override;
void Draw(Bitmap *ds, int x = 0, int y = 0) override;
void SetText(const String &text);
// Serialization
void ReadFromFile(Stream *in, GuiVersion gui_version) override;
void WriteToFile(Stream *out) const override;
void ReadFromSavegame(Shared::Stream *in, GuiSvgVersion svg_ver) override;
void WriteToSavegame(Shared::Stream *out) const override;
// TODO: these members are currently public; hide them later
public:
String Text;
int32_t Font;
color_t TextColor;
HorAlignment TextAlignment;
private:
// Transforms the Text property to a drawn text, applies translation,
// replaces macros, splits and wraps, etc; returns number of lines.
int PrepareTextToDraw();
// Information on macros contained within Text field
GUILabelMacro _textMacro;
// prepared text buffer/cache
// TODO: cache split lines instead?
String _textToDraw;
};
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,453 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/std/algorithm.h"
#include "ags/shared/gui/gui_listbox.h"
#include "ags/shared/ac/game_version.h"
#include "ags/shared/font/fonts.h"
#include "ags/shared/gui/gui_main.h"
#include "ags/shared/util/stream.h"
#include "ags/shared/util/string_utils.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
GUIListBox::GUIListBox() {
ItemCount = 0;
SelectedItem = 0;
TopItem = 0;
RowHeight = 0;
VisibleItemCount = 0;
Font = 0;
TextColor = 0;
SelectedTextColor = 7;
ListBoxFlags = kListBox_DefFlags;
SelectedBgColor = 16;
TextAlignment = kHAlignLeft;
_scEventCount = 1;
_scEventNames[0] = "SelectionChanged";
_scEventArgs[0] = "GUIControl *control";
}
bool GUIListBox::HasAlphaChannel() const {
return is_font_antialiased(Font);
}
int GUIListBox::GetItemAt(int x, int y) const {
if (RowHeight <= 0 || IsInRightMargin(x))
return -1;
int index = y / RowHeight + TopItem;
if (index < 0 || index >= ItemCount)
return -1;
return index;
}
bool GUIListBox::AreArrowsShown() const {
return (ListBoxFlags & kListBox_ShowArrows) != 0;
}
bool GUIListBox::IsBorderShown() const {
return (ListBoxFlags & kListBox_ShowBorder) != 0;
}
bool GUIListBox::IsSvgIndex() const {
return (ListBoxFlags & kListBox_SvgIndex) != 0;
}
bool GUIListBox::IsInRightMargin(int x) const {
if (x >= (_width - get_fixed_pixel_size(6)) && IsBorderShown() && AreArrowsShown())
return 1;
return 0;
}
Rect GUIListBox::CalcGraphicRect(bool clipped) {
if (clipped)
return RectWH(0, 0, _width, _height);
// TODO: need to find a way to text position, or there'll be some repetition
// have to precache text and size on some events:
// - translation change
// - macro value change (score, overhotspot etc)
Rect rc = RectWH(0, 0, _width, _height);
UpdateMetrics();
const int width = _width - 1;
const int pixel_size = get_fixed_pixel_size(1);
int right_hand_edge = width - pixel_size - 1;
// calculate the scroll bar's width if necessary
if (ItemCount > VisibleItemCount &&IsBorderShown() && AreArrowsShown())
right_hand_edge -= get_fixed_pixel_size(7);
Line max_line;
for (int item = 0; (item < VisibleItemCount) && (item + TopItem < ItemCount); ++item) {
int at_y = pixel_size + item * RowHeight;
int item_index = item + TopItem;
PrepareTextToDraw(Items[item_index]);
Line lpos = GUI::CalcTextPositionHor(_textToDraw.GetCStr(), Font, 1 + pixel_size, right_hand_edge, at_y + 1,
(FrameAlignment)TextAlignment);
max_line.X2 = MAX(max_line.X2, lpos.X2);
}
int last_line_y = pixel_size + 1 + (VisibleItemCount - 1) * RowHeight;
// Include font fixes for the first and last text line,
// in case graphical height is different, and there's a VerticalOffset
Line vextent = GUI::CalcFontGraphicalVExtent(Font);
Rect text_rc = RectWH(0, vextent.Y1, max_line.X2 - max_line.X1 + 1, last_line_y + (vextent.Y2 - vextent.Y1));
return SumRects(rc, text_rc);
}
int GUIListBox::AddItem(const String &text) {
Items.push_back(text);
SavedGameIndex.push_back(-1);
ItemCount++;
MarkChanged();
return ItemCount - 1;
}
void GUIListBox::Clear() {
if (Items.empty())
return;
Items.clear();
SavedGameIndex.clear();
ItemCount = 0;
SelectedItem = 0;
TopItem = 0;
MarkChanged();
}
void GUIListBox::Draw(Bitmap *ds, int x, int y) {
const int width = _width - 1;
const int height = _height - 1;
const int pixel_size = get_fixed_pixel_size(1);
color_t text_color = ds->GetCompatibleColor(TextColor);
color_t draw_color = ds->GetCompatibleColor(TextColor);
if (IsBorderShown()) {
ds->DrawRect(Rect(x, y, x + width, y + height), draw_color);
if (pixel_size > 1)
ds->DrawRect(Rect(x + 1, y + 1, x + width - 1, y + height - 1), draw_color);
}
int right_hand_edge = (x + width) - pixel_size - 1;
// update the RowHeight and VisibleItemCount
// FIXME: find a way to update this whenever relevant things change in the engine
UpdateMetrics();
// draw the scroll bar in if necessary
bool scrollbar = (ItemCount > VisibleItemCount) && IsBorderShown() && AreArrowsShown();
if (scrollbar) {
int xstrt, ystrt;
ds->DrawRect(Rect(x + width - get_fixed_pixel_size(7), y, (x + (pixel_size - 1) + width) - get_fixed_pixel_size(7), y + height), draw_color);
ds->DrawRect(Rect(x + width - get_fixed_pixel_size(7), y + height / 2, x + width, y + height / 2 + (pixel_size - 1)), draw_color);
xstrt = (x + width - get_fixed_pixel_size(6)) + (pixel_size - 1);
ystrt = (y + height - 3) - get_fixed_pixel_size(5);
draw_color = ds->GetCompatibleColor(TextColor);
ds->DrawTriangle(Triangle(xstrt, ystrt, xstrt + get_fixed_pixel_size(4), ystrt,
xstrt + get_fixed_pixel_size(2),
ystrt + get_fixed_pixel_size(5)), draw_color);
ystrt = y + 3;
ds->DrawTriangle(Triangle(xstrt, ystrt + get_fixed_pixel_size(5),
xstrt + get_fixed_pixel_size(4),
ystrt + get_fixed_pixel_size(5),
xstrt + get_fixed_pixel_size(2), ystrt), draw_color);
right_hand_edge -= get_fixed_pixel_size(7);
}
Rect old_clip = ds->GetClip();
if (scrollbar && GUI::Options.ClipControls)
ds->SetClip(Rect(x, y, right_hand_edge + 1, y + _height - 1));
for (int item = 0; (item < VisibleItemCount) && (item + TopItem < ItemCount); ++item) {
int at_y = y + pixel_size + item * RowHeight;
if (item + TopItem == SelectedItem) {
text_color = ds->GetCompatibleColor(SelectedTextColor);
if (SelectedBgColor > 0) {
int stretch_to = (x + width) - pixel_size;
// draw the SelectedItem item bar (if colour not transparent)
draw_color = ds->GetCompatibleColor(SelectedBgColor);
if ((VisibleItemCount < ItemCount) && IsBorderShown() && AreArrowsShown())
stretch_to -= get_fixed_pixel_size(7);
ds->FillRect(Rect(x + pixel_size, at_y, stretch_to, at_y + RowHeight - pixel_size), draw_color);
}
} else
text_color = ds->GetCompatibleColor(TextColor);
int item_index = item + TopItem;
PrepareTextToDraw(Items[item_index]);
GUI::DrawTextAlignedHor(ds, _textToDraw.GetCStr(), Font, text_color, x + 1 + pixel_size, right_hand_edge, at_y + 1,
(FrameAlignment)TextAlignment);
}
ds->SetClip(old_clip);
}
int GUIListBox::InsertItem(int index, const String &text) {
if (index < 0 || index > ItemCount)
return -1;
Items.insert(Items.begin() + index, text);
SavedGameIndex.insert(SavedGameIndex.begin() + index, -1);
if (SelectedItem >= index)
SelectedItem++;
ItemCount++;
MarkChanged();
return ItemCount - 1;
}
void GUIListBox::RemoveItem(int index) {
if (index < 0 || index >= ItemCount)
return;
Items.erase(Items.begin() + index);
SavedGameIndex.erase(SavedGameIndex.begin() + index);
ItemCount--;
if (SelectedItem > index)
SelectedItem--;
if (SelectedItem >= ItemCount)
SelectedItem = -1;
MarkChanged();
}
void GUIListBox::SetShowArrows(bool on) {
if (on != ((ListBoxFlags & kListBox_ShowArrows) != 0))
MarkChanged();
if (on)
ListBoxFlags |= kListBox_ShowArrows;
else
ListBoxFlags &= ~kListBox_ShowArrows;
}
void GUIListBox::SetShowBorder(bool on) {
if (on != ((ListBoxFlags & kListBox_ShowBorder) != 0))
MarkChanged();
if (on)
ListBoxFlags |= kListBox_ShowBorder;
else
ListBoxFlags &= ~kListBox_ShowBorder;
}
void GUIListBox::SetSvgIndex(bool on) {
if (on)
ListBoxFlags |= kListBox_SvgIndex;
else
ListBoxFlags &= ~kListBox_SvgIndex;
}
void GUIListBox::SetFont(int font) {
if (Font == font)
return;
Font = font;
UpdateMetrics();
MarkChanged();
}
void GUIListBox::SetItemText(int index, const String &text) {
if ((index >= 0) && (index < ItemCount) && (text != Items[index])) {
Items[index] = text;
MarkChanged();
}
}
bool GUIListBox::OnMouseDown() {
if (IsInRightMargin(MousePos.X)) {
int top_item = TopItem;
if (MousePos.Y < _height / 2 && TopItem > 0)
top_item = TopItem - 1;
if (MousePos.Y >= _height / 2 && ItemCount > TopItem + VisibleItemCount)
top_item = TopItem + 1;
if (TopItem != top_item) {
TopItem = top_item;
MarkChanged();
}
return false;
}
int sel = GetItemAt(MousePos.X, MousePos.Y);
if (sel < 0)
return false;
if (sel != SelectedItem) {
SelectedItem = sel;
MarkChanged();
}
IsActivated = true;
return false;
}
void GUIListBox::OnMouseMove(int x_, int y_) {
MousePos.X = x_ - X;
MousePos.Y = y_ - Y;
}
void GUIListBox::OnResized() {
UpdateMetrics();
MarkChanged();
}
void GUIListBox::UpdateMetrics() {
int font_height = (_G(loaded_game_file_version) < kGameVersion_360_21) ?
get_font_height(Font) : get_font_height_outlined(Font);
RowHeight = font_height + get_fixed_pixel_size(2); // +1 top/bottom margin
VisibleItemCount = _height / RowHeight;
if (ItemCount <= VisibleItemCount)
TopItem = 0; // reset scroll if all items are visible
}
// TODO: replace string serialization with StrUtil::ReadString and WriteString
// methods in the future, to keep this organized.
void GUIListBox::WriteToFile(Stream *out) const {
GUIObject::WriteToFile(out);
out->WriteInt32(ItemCount);
out->WriteInt32(Font);
out->WriteInt32(TextColor);
out->WriteInt32(SelectedTextColor);
out->WriteInt32(ListBoxFlags);
out->WriteInt32(TextAlignment);
out->WriteInt32(SelectedBgColor);
for (int i = 0; i < ItemCount; ++i)
Items[i].Write(out);
}
void GUIListBox::ReadFromFile(Stream *in, GuiVersion gui_version) {
Clear();
GUIObject::ReadFromFile(in, gui_version);
ItemCount = in->ReadInt32();
if (gui_version < kGuiVersion_350) { // NOTE: reading into actual variables only for old savegame support
SelectedItem = in->ReadInt32();
TopItem = in->ReadInt32();
MousePos.X = in->ReadInt32();
MousePos.Y = in->ReadInt32();
RowHeight = in->ReadInt32();
VisibleItemCount = in->ReadInt32();
}
Font = in->ReadInt32();
TextColor = in->ReadInt32();
SelectedTextColor = in->ReadInt32();
ListBoxFlags = in->ReadInt32();
// reverse particular flags from older format
if (gui_version < kGuiVersion_350)
ListBoxFlags ^= kListBox_OldFmtXorMask;
if (gui_version >= kGuiVersion_272b) {
if (gui_version < kGuiVersion_350) {
TextAlignment = ConvertLegacyGUIAlignment((LegacyGUIAlignment)in->ReadInt32());
in->ReadInt32(); // reserved1
} else {
TextAlignment = (HorAlignment)in->ReadInt32();
}
} else {
TextAlignment = kHAlignLeft;
}
if (gui_version >= kGuiVersion_unkn_107) {
SelectedBgColor = in->ReadInt32();
} else {
SelectedBgColor = TextColor;
if (SelectedBgColor == 0)
SelectedBgColor = 16;
}
// NOTE: we leave items in game data format as a potential support for defining
// ListBox contents at design-time, although Editor does not support it as of 3.5.0.
Items.resize(ItemCount);
SavedGameIndex.resize(ItemCount, -1);
for (int i = 0; i < ItemCount; ++i) {
Items[i].Read(in);
}
if (gui_version >= kGuiVersion_272d && gui_version < kGuiVersion_350 &&
(ListBoxFlags & kListBox_SvgIndex)) { // NOTE: reading into actual variables only for old savegame support
for (int i = 0; i < ItemCount; ++i)
SavedGameIndex[i] = in->ReadInt16();
}
if (TextColor == 0)
TextColor = 16;
UpdateMetrics();
}
void GUIListBox::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) {
GUIObject::ReadFromSavegame(in, svg_ver);
// Properties
ListBoxFlags = in->ReadInt32();
Font = in->ReadInt32();
if (svg_ver < kGuiSvgVersion_350) {
// reverse particular flags from older format
ListBoxFlags ^= kListBox_OldFmtXorMask;
} else {
SelectedBgColor = in->ReadInt32();
SelectedTextColor = in->ReadInt32();
TextAlignment = (HorAlignment)in->ReadInt32();
TextColor = in->ReadInt32();
}
// Items
ItemCount = in->ReadInt32();
Items.resize(ItemCount);
SavedGameIndex.resize(ItemCount);
for (int i = 0; i < ItemCount; ++i)
Items[i] = StrUtil::ReadString(in);
// TODO: investigate this, it might be unreasonable to save and read
// savegame index like that because list of savegames may easily change
// in between writing and restoring the game. Perhaps clearing and forcing
// this list to update on load somehow may make more sense.
if (ListBoxFlags & kListBox_SvgIndex)
for (int i = 0; i < ItemCount; ++i)
SavedGameIndex[i] = in->ReadInt16();
TopItem = in->ReadInt32();
SelectedItem = in->ReadInt32();
UpdateMetrics();
}
void GUIListBox::WriteToSavegame(Stream *out) const {
GUIObject::WriteToSavegame(out);
// Properties
out->WriteInt32(ListBoxFlags);
out->WriteInt32(Font);
out->WriteInt32(SelectedBgColor);
out->WriteInt32(SelectedTextColor);
out->WriteInt32(TextAlignment);
out->WriteInt32(TextColor);
// Items
out->WriteInt32(ItemCount);
for (int i = 0; i < ItemCount; ++i)
StrUtil::WriteString(Items[i], out);
if (ListBoxFlags & kListBox_SvgIndex)
for (int i = 0; i < ItemCount; ++i)
out->WriteInt16(SavedGameIndex[i]);
out->WriteInt32(TopItem);
out->WriteInt32(SelectedItem);
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,103 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_GUI_GUI_LISTBOX_H
#define AGS_SHARED_GUI_GUI_LISTBOX_H
#include "common/std/vector.h"
#include "ags/shared/gui/gui_object.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class GUIListBox : public GUIObject {
public:
GUIListBox();
bool HasAlphaChannel() const override;
bool AreArrowsShown() const;
bool IsBorderShown() const;
bool IsSvgIndex() const;
bool IsInRightMargin(int x) const;
int GetItemAt(int x, int y) const;
// Operations
int AddItem(const String &text);
void Clear();
Rect CalcGraphicRect(bool clipped) override;
void Draw(Bitmap *ds, int x = 0, int y = 0) override;
int InsertItem(int index, const String &text);
void RemoveItem(int index);
void SetShowArrows(bool on);
void SetShowBorder(bool on);
void SetSvgIndex(bool on); // TODO: work around this
void SetFont(int font);
void SetItemText(int index, const String &textt);
// Events
bool OnMouseDown() override;
void OnMouseMove(int x, int y) override;
void OnResized() override;
// Serialization
void ReadFromFile(Stream *in, GuiVersion gui_version) override;
void WriteToFile(Stream *out) const override;
void ReadFromSavegame(Shared::Stream *in, GuiSvgVersion svg_ver) override;
void WriteToSavegame(Shared::Stream *out) const override;
// TODO: these members are currently public; hide them later
public:
int32_t Font;
color_t TextColor;
HorAlignment TextAlignment;
color_t SelectedBgColor;
color_t SelectedTextColor;
int32_t RowHeight;
int32_t VisibleItemCount;
std::vector<String> Items;
std::vector<int16_t> SavedGameIndex;
int32_t SelectedItem;
int32_t TopItem;
Point MousePos;
// TODO: remove these later
int32_t ItemCount;
private:
int32_t ListBoxFlags;
// Updates dynamic metrics such as row height and others
void UpdateMetrics();
// Applies translation
void PrepareTextToDraw(const String &text);
// prepared text buffer/cache
String _textToDraw;
};
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,301 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_GUI_GUI_MAIN_H
#define AGS_SHARED_GUI_GUI_MAIN_H
#include "common/std/vector.h"
#include "ags/engine/ac/draw.h"
#include "ags/shared/ac/common.h"
#include "ags/shared/ac/common_defines.h" // TODO: split out gui drawing helpers
#include "ags/shared/gfx/gfx_def.h" // TODO: split out gui drawing helpers
#include "ags/shared/gui/gui_defines.h"
#include "ags/shared/util/error.h"
#include "ags/shared/util/geometry.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
// Forward declaration
namespace AGS {
namespace Shared {
class Stream;
}
}
using namespace AGS; // FIXME later
class SplitLines;
#define LEGACY_MAX_OBJS_ON_GUI 30
#define GUIMAIN_LEGACY_RESERVED_INTS 5
#define GUIMAIN_LEGACY_NAME_LENGTH 16
#define GUIMAIN_LEGACY_EVENTHANDLER_LENGTH 20
#define GUIMAIN_LEGACY_TW_FLAGS_SIZE 4
namespace AGS {
namespace Shared {
// Legacy GUIMain visibility state, which combined Visible property and override factor
enum LegacyGUIVisState {
kGUIVisibility_LockedOff = -1, // locked hidden (used by PopupMouseY guis)
kGUIVisibility_Off = 0, // hidden
kGUIVisibility_On = 1 // shown
};
class Bitmap;
class GUIObject;
class GUIMain {
public:
static String FixupGUIName(const String &name);
public:
GUIMain();
void InitDefaults();
// Tells if the gui background supports alpha channel
bool HasAlphaChannel() const;
// Tells if GUI will react on clicking on it
bool IsClickable() const { return (_flags & kGUIMain_Clickable) != 0; }
// Tells if GUI's visibility is overridden and it won't be displayed on
// screen regardless of Visible property (until concealed mode is off).
bool IsConcealed() const { return (_flags & kGUIMain_Concealed) != 0; }
// Tells if gui is actually meant to be displayed on screen.
// Normally Visible property determines whether GUI is allowed to be seen,
// but there may be other settings that override it.
bool IsDisplayed() const { return IsVisible() && !IsConcealed(); }
// Tells if given coordinates are within interactable area of gui
// NOTE: this currently tests for actual visibility and Clickable property
bool IsInteractableAt(int x, int y) const;
// Tells if gui is a text window
bool IsTextWindow() const { return (_flags & kGUIMain_TextWindow) != 0; }
// Tells if GUI is *allowed* to be displayed and interacted with.
// This does not necessarily mean that it is displayed right now, because
// GUI may be hidden for other reasons, including overriding behavior.
// For example GUI with kGUIPopupMouseY style will not be shown unless
// mouse cursor is at certain position on screen.
bool IsVisible() const { return (_flags & kGUIMain_Visible) != 0; }
// Tells if GUI has graphically changed recently
bool HasChanged() const { return _hasChanged; }
bool HasControlsChanged() const { return _hasControlsChanged; }
// Manually marks GUI as graphically changed
// NOTE: this only matters if GUI's own graphic changes (content, size etc),
// but not its state (visible) or texture drawing mode (transparency, etc).
void MarkChanged();
// Marks GUI as having any of its controls changed its looks.
void MarkControlChanged();
// Clears changed flag
void ClearChanged();
// Notify GUI about any of its controls changing its location.
void NotifyControlPosition();
// Notify GUI about one of its controls changing its interactive state.
void NotifyControlState(int objid, bool mark_changed);
// Resets control-under-mouse detection.
void ResetOverControl();
// Finds a control under given screen coordinates, returns control's child ID.
// Optionally allows extra leeway (offset in all directions) to let the user grab tiny controls.
// Optionally only allows clickable controls, ignoring non-clickable ones.
int32_t FindControlAt(int atx, int aty, int leeway = 0, bool must_be_clickable = true) const;
// Gets the number of the GUI child controls
int32_t GetControlCount() const;
// Gets control by its child's index
GUIObject *GetControl(int32_t index) const;
// Gets child control's type, looks up with child's index
GUIControlType GetControlType(int32_t index) const;
// Gets child control's global ID, looks up with child's index
int32_t GetControlID(int32_t index) const;
// Gets an array of child control indexes in the z-order, from bottom to top
const std::vector<int> &GetControlsDrawOrder() const;
// Child control management
// Note that currently GUIMain does not own controls (should not delete them)
void AddControl(GUIControlType type, int32_t id, GUIObject *control);
void RemoveAllControls();
// Operations
bool BringControlToFront(int32_t index);
void DrawSelf(Bitmap *ds);
void DrawWithControls(Bitmap *ds);
// Polls GUI state, providing current cursor (mouse) coordinates
void Poll(int mx, int my);
HError RebuildArray();
void ResortZOrder();
bool SendControlToBack(int32_t index);
// Sets whether GUI should react to player clicking on it
void SetClickable(bool on);
// Override GUI visibility; when in concealed mode GUI won't show up
// even if Visible = true
void SetConceal(bool on);
// Attempts to change control's zorder; returns if zorder changed
bool SetControlZOrder(int32_t index, int zorder);
// Changes GUI style to the text window or back
void SetTextWindow(bool on);
// Sets GUI transparency as a percentage (0 - 100) where 100 = invisible
void SetTransparencyAsPercentage(int percent);
// Sets whether GUI is allowed to be displayed on screen
void SetVisible(bool on);
// Events
void OnMouseButtonDown(int mx, int my);
void OnMouseButtonUp();
// Serialization
void ReadFromFile(Stream *in, GuiVersion gui_version);
void WriteToFile(Stream *out) const;
// TODO: move to engine, into gui savegame component unit
// (should read/write GUI properties accessing them by interface)
void ReadFromSavegame(Stream *in, GuiSvgVersion svg_version);
void WriteToSavegame(Stream *out) const;
private:
void DrawBlob(Bitmap *ds, int x, int y, color_t draw_color);
// Same as FindControlAt but expects local space coordinates
int32_t FindControlAtLocal(int atx, int aty, int leeway, bool must_be_clickable) const;
// TODO: all members are currently public; hide them later
public:
int32_t ID; // GUI identifier
String Name; // the name of the GUI
int32_t X;
int32_t Y;
int32_t Width;
int32_t Height;
color_t BgColor; // background color
int32_t BgImage; // background sprite index
color_t FgColor; // foreground color (used as border color in normal GUIs,
// and text color in text windows)
int32_t Padding; // padding surrounding a GUI text window
GUIPopupStyle PopupStyle; // GUI popup behavior
int32_t PopupAtMouseY; // popup when _G(mousey) < this
int32_t Transparency; // "incorrect" alpha (in legacy 255-range units)
int32_t ZOrder;
int32_t FocusCtrl; // which control has the focus
int32_t HighlightCtrl; // which control has the bounding selection rect
int32_t MouseOverCtrl; // which control has the mouse cursor over it
int32_t MouseDownCtrl; // which control has the mouse button pressed on it
Point MouseWasAt; // last mouse cursor position
String OnClickHandler; // script function name
private:
int32_t _flags; // style and behavior flags
bool _hasChanged; // flag tells whether GUI has graphically changed recently
bool _hasControlsChanged;
bool _polling; // inside the polling process
// Array of types and control indexes in global GUI object arrays;
// maps GUI child slots to actual controls and used for rebuilding Controls array
typedef std::pair<GUIControlType, int32_t> ControlRef;
std::vector<ControlRef> _ctrlRefs;
// Array of child control references (not exclusively owned!)
std::vector<GUIObject *> _controls;
// Sorted array of controls in z-order.
std::vector<int> _ctrlDrawOrder;
};
namespace GUI {
extern GuiVersion GameGuiVersion;
extern GuiOptions Options;
// Applies current text direction setting (may depend on multiple factors)
String ApplyTextDirection(const String &text);
// Calculates the text's draw position, given the alignment
// optionally returns the real graphical rect that the text would occupy
Point CalcTextPosition(const char *text, int font, const Rect &frame, FrameAlignment align, Rect *gr_rect = nullptr);
// Calculates the text's draw position and horizontal extent,
// using strictly horizontal alignment
Line CalcTextPositionHor(const char *text, int font, int x1, int x2, int y, FrameAlignment align);
// Calculates the graphical rect that the text would occupy
// if drawn at the given coordinates
Rect CalcTextGraphicalRect(const char *text, int font, const Point &at);
// Calculates the graphical rect that the text would occupy
// if drawn aligned to the given frame
Rect CalcTextGraphicalRect(const char *text, int font, const Rect &frame, FrameAlignment align);
// Calculates a vertical graphical extent for a given font,
// which is a top and bottom offsets in zero-based coordinates.
// NOTE: this applies font size fixups.
Line CalcFontGraphicalVExtent(int font);
// Draw standart "shading" effect over rectangle
void DrawDisabledEffect(Bitmap *ds, const Rect &rc);
// Draw text aligned inside rectangle
void DrawTextAligned(Bitmap *ds, const char *text, int font, color_t text_color, const Rect &frame, FrameAlignment align);
// Draw text aligned horizontally inside given bounds
void DrawTextAlignedHor(Bitmap *ds, const char *text, int font, color_t text_color, int x1, int x2, int y, FrameAlignment align);
// Parses the string and returns combination of label macro flags
GUILabelMacro FindLabelMacros(const String &text);
// Applies text transformation necessary for rendering, in accordance to the
// current game settings, such as right-to-left render, and anything else
String TransformTextForDrawing(const String &text, bool translate, bool apply_direction);
// Wraps given text to make it fit into width, stores it in the lines;
// apply_direction param tells whether text direction setting should be applied
size_t SplitLinesForDrawing(const char *text, bool apply_direction, SplitLines &lines, int font, int width, size_t max_lines = -1);
// Mark all existing GUI for redraw
void MarkAllGUIForUpdate(bool redraw, bool reset_over_ctrl);
// Mark all translatable GUI controls for redraw
void MarkForTranslationUpdate();
// Mark all GUI which use the given font for recalculate/redraw;
// pass -1 to update all the textual controls together
void MarkForFontUpdate(int font);
// Mark labels that acts as special text placeholders for redraw
void MarkSpecialLabelsForUpdate(GUILabelMacro macro);
// Mark inventory windows for redraw, optionally only ones linked to given character;
// also marks buttons with inventory icon mode
void MarkInventoryForUpdate(int char_id, bool is_player);
// Reads all GUIs and their controls.
// WARNING: the data is read into the global arrays (guis, guibuts, and so on)
// TODO: remove is_savegame param after dropping support for old saves
// because only they use ReadGUI to read runtime GUI data
HError ReadGUI(Stream *in, bool is_savegame = false);
// Writes all GUIs and their controls.
// WARNING: the data is written from the global arrays (guis, guibuts, and so on)
void WriteGUI(Stream *out);
// Converts legacy GUIVisibility into appropriate GUIMain properties
void ApplyLegacyVisibility(GUIMain &gui, LegacyGUIVisState vis);
// Rebuilds GUIs, connecting them to the child controls in memory.
// WARNING: the data is processed in the global arrays (guis, guibuts, and so on)
HError RebuildGUI();
}
} // namespace Shared
} // namespace AGS
extern int get_adjusted_spritewidth(int spr);
extern int get_adjusted_spriteheight(int spr);
extern bool is_sprite_alpha(int spr);
extern void set_eip_guiobj(int eip);
extern int get_eip_guiobj();
} // namespace AGS3
#endif

View File

@@ -0,0 +1,223 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/ac/common.h" // quit
#include "ags/shared/gui/gui_main.h"
#include "ags/shared/gui/gui_object.h"
#include "ags/shared/util/stream.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
GUIObject::GUIObject() {
Id = 0;
ParentId = 0;
Flags = kGUICtrl_DefFlags;
X = 0;
Y = 0;
_width = 0;
_height = 0;
ZOrder = -1;
IsActivated = false;
_transparency = 0;
_scEventCount = 0;
_hasChanged = true;
}
String GUIObject::GetScriptName() const {
return Name;
}
int GUIObject::GetEventCount() const {
return _scEventCount;
}
String GUIObject::GetEventName(int event) const {
if (event < 0 || event >= _scEventCount)
return "";
return _scEventNames[event];
}
String GUIObject::GetEventArgs(int event) const {
if (event < 0 || event >= _scEventCount)
return "";
return _scEventArgs[event];
}
bool GUIObject::IsOverControl(int x, int y, int leeway) const {
return x >= X && y >= Y && x < (X + _width + leeway) && y < (Y + _height + leeway);
}
void GUIObject::SetClickable(bool on) {
if (on != ((Flags & kGUICtrl_Clickable) != 0)) {
Flags = (Flags & ~kGUICtrl_Clickable) | kGUICtrl_Clickable * on;
MarkStateChanged(false, false); // update cursor-over-control only
}
}
void GUIObject::SetEnabled(bool on) {
if (on != ((Flags & kGUICtrl_Enabled) != 0)) {
Flags = (Flags & ~kGUICtrl_Enabled) | kGUICtrl_Enabled * on;
MarkStateChanged(true, true); // may change looks, and update cursor-over-control
}
}
void GUIObject::SetSize(int width, int height) {
if (_width != width || _height != height) {
_width = width;
_height = height;
OnResized();
}
}
void GUIObject::SetTranslated(bool on) {
if (on != ((Flags & kGUICtrl_Translated) != 0)) {
Flags = (Flags & ~kGUICtrl_Translated) | kGUICtrl_Translated * on;
MarkChanged();
}
}
void GUIObject::SetVisible(bool on) {
if (on != ((Flags & kGUICtrl_Visible) != 0)) {
Flags = (Flags & ~kGUICtrl_Visible) | kGUICtrl_Visible * on;
MarkStateChanged(false, true); // for software mode, and to update cursor-over-control
}
}
void GUIObject::SetTransparency(int trans) {
if (_transparency != trans) {
_transparency = trans;
MarkParentChanged(); // for software mode
}
}
// TODO: replace string serialization with StrUtil::ReadString and WriteString
// methods in the future, to keep this organized.
void GUIObject::WriteToFile(Stream *out) const {
out->WriteInt32(Flags);
out->WriteInt32(X);
out->WriteInt32(Y);
out->WriteInt32(_width);
out->WriteInt32(_height);
out->WriteInt32(ZOrder);
Name.Write(out);
out->WriteInt32(_scEventCount);
for (int i = 0; i < _scEventCount; ++i)
EventHandlers[i].Write(out);
}
void GUIObject::ReadFromFile(Stream *in, GuiVersion gui_version) {
Flags = in->ReadInt32();
// reverse particular flags from older format
if (gui_version < kGuiVersion_350)
Flags ^= kGUICtrl_OldFmtXorMask;
X = in->ReadInt32();
Y = in->ReadInt32();
_width = in->ReadInt32();
_height = in->ReadInt32();
ZOrder = in->ReadInt32();
if (gui_version < kGuiVersion_350) { // NOTE: reading into actual variables only for old savegame support
IsActivated = in->ReadInt32() != 0;
}
if (gui_version >= kGuiVersion_unkn_106)
Name.Read(in);
else
Name.Free();
for (int i = 0; i < _scEventCount; ++i) {
EventHandlers[i].Free();
}
if (gui_version >= kGuiVersion_unkn_108) {
int evt_count = in->ReadInt32();
if (evt_count > _scEventCount)
quit("Error: too many control events, need newer version");
for (int i = 0; i < evt_count; ++i) {
EventHandlers[i].Read(in);
}
}
}
void GUIObject::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) {
// Properties
Flags = in->ReadInt32();
// reverse particular flags from older format
if (svg_ver < kGuiSvgVersion_350)
Flags ^= kGUICtrl_OldFmtXorMask;
X = in->ReadInt32();
Y = in->ReadInt32();
_width = in->ReadInt32();
_height = in->ReadInt32();
ZOrder = in->ReadInt32();
// Dynamic state
IsActivated = in->ReadBool() ? 1 : 0;
if (svg_ver >= kGuiSvgVersion_36023) {
_transparency = in->ReadInt32();
in->ReadInt32(); // reserve 3 ints
in->ReadInt32();
in->ReadInt32();
}
}
void GUIObject::WriteToSavegame(Stream *out) const {
// Properties
out->WriteInt32(Flags);
out->WriteInt32(X);
out->WriteInt32(Y);
out->WriteInt32(_width);
out->WriteInt32(_height);
out->WriteInt32(ZOrder);
// Dynamic state
out->WriteBool(IsActivated != 0);
out->WriteInt32(_transparency);
out->WriteInt32(0); // reserve 3 ints
out->WriteInt32(0);
out->WriteInt32(0);
}
HorAlignment ConvertLegacyGUIAlignment(LegacyGUIAlignment align) {
switch (align) {
case kLegacyGUIAlign_Right:
return kHAlignRight;
case kLegacyGUIAlign_Center:
return kHAlignCenter;
default:
return kHAlignLeft;
}
}
LegacyGUIAlignment GetLegacyGUIAlignment(HorAlignment align) {
switch (align) {
case kHAlignRight:
return kLegacyGUIAlign_Right;
case kHAlignCenter:
return kLegacyGUIAlign_Center;
default:
return kLegacyGUIAlign_Left;
}
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,172 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_GUI_GUI_OBJECT_H
#define AGS_SHARED_GUI_GUI_OBJECT_H
#include "ags/shared/core/types.h"
#include "ags/shared/gfx/bitmap.h"
#include "ags/shared/gui/gui_defines.h"
#include "ags/shared/util/string.h"
#include "ags/globals.h"
namespace AGS3 {
struct KeyInput;
namespace AGS {
namespace Shared {
enum LegacyGUIAlignment {
kLegacyGUIAlign_Left = 0,
kLegacyGUIAlign_Right = 1,
kLegacyGUIAlign_Center = 2
};
class GUIObject {
public:
GUIObject();
virtual ~GUIObject() {}
String GetScriptName() const;
String GetEventArgs(int event) const;
int GetEventCount() const;
String GetEventName(int event) const;
bool IsClickable() const { return (Flags & kGUICtrl_Clickable) != 0; }
bool IsDeleted() const { return (Flags & kGUICtrl_Deleted) != 0; }
bool IsEnabled() const { return (Flags & kGUICtrl_Enabled) != 0; }
bool IsTranslated() const { return (Flags & kGUICtrl_Translated) != 0; }
bool IsVisible() const { return (Flags & kGUICtrl_Visible) != 0; }
// overridable routine to determine whether the mouse is over the control
virtual bool IsOverControl(int x, int y, int leeway) const;
Size GetSize() const { return Size(_width, _height); }
int GetWidth() const { return _width; }
int GetHeight() const { return _height; }
int GetTransparency() const { return _transparency; }
// Compatibility: should the control's graphic be clipped to its x,y,w,h
virtual bool IsContentClipped() const { return true; }
// Tells if the object image supports alpha channel
virtual bool HasAlphaChannel() const { return false; }
// Operations
// Returns the (untransformed!) visual rectangle of this control,
// in *relative* coordinates, optionally clipped by the logical size
virtual Rect CalcGraphicRect(bool /*clipped*/) {
return RectWH(0, 0, _width, _height);
}
virtual void Draw(Bitmap *ds, int x = 0, int y = 0) {
(void)ds; (void)x; (void)y;
}
void SetClickable(bool on);
void SetEnabled(bool on);
void SetSize(int width, int height);
inline void SetWidth(int width) { SetSize(width, _height); }
inline void SetHeight(int height) { SetSize(_width, height); }
void SetTranslated(bool on);
void SetVisible(bool on);
void SetTransparency(int trans);
// Events
// Key pressed for control
virtual void OnKeyPress(const KeyInput &) {}
// Mouse button down - return 'True' to lock focus
virtual bool OnMouseDown() {
return false;
}
// Mouse moves onto control
virtual void OnMouseEnter() {
}
// Mouse moves off control
virtual void OnMouseLeave() {
}
// Mouse moves over control - x,y relative to gui
virtual void OnMouseMove(int /*x*/, int /*y*/) {
}
// Mouse button up
virtual void OnMouseUp() {
}
// Control was resized
virtual void OnResized() { MarkPositionChanged(true); }
// Serialization
virtual void ReadFromFile(Shared::Stream *in, GuiVersion gui_version);
virtual void WriteToFile(Shared::Stream *out) const;
virtual void ReadFromSavegame(Shared::Stream *in, GuiSvgVersion svg_ver);
virtual void WriteToSavegame(Shared::Stream *out) const;
// TODO: these members are currently public; hide them later
public:
// Manually marks GUIObject as graphically changed
// NOTE: this only matters if control's own graphic changes, but not its
// logical (visible, clickable, etc) or visual (e.g. transparency) state.
void MarkChanged();
// Notifies parent GUI that this control has changed its visual state
void MarkParentChanged();
// Notifies parent GUI that this control has changed its location (pos, size)
void MarkPositionChanged(bool self_changed);
// Notifies parent GUI that this control's interactive state has changed
void MarkStateChanged(bool self_changed, bool parent_changed);
bool HasChanged() const { return _hasChanged; };
void ClearChanged();
int32_t Id; // GUI object's identifier
int32_t ParentId; // id of parent GUI
String Name; // script name
int32_t X;
int32_t Y;
int32_t ZOrder;
bool IsActivated; // signals user interaction
String EventHandlers[MAX_GUIOBJ_EVENTS]; // script function names
protected:
uint32_t Flags; // generic style and behavior flags
int32_t _width;
int32_t _height;
int32_t _transparency; // "incorrect" alpha (in legacy 255-range units)
bool _hasChanged;
// TODO: explicit event names & handlers for every event
// FIXME: these must be static!! per type
int32_t _scEventCount; // number of supported script events
String _scEventNames[MAX_GUIOBJ_EVENTS]; // script event names
String _scEventArgs[MAX_GUIOBJ_EVENTS]; // script handler params
};
// Converts legacy alignment type used in GUI Label/ListBox data (only left/right/center)
HorAlignment ConvertLegacyGUIAlignment(LegacyGUIAlignment align);
LegacyGUIAlignment GetLegacyGUIAlignment(HorAlignment align);
} // namespace Shared
} // namespace AGS
// Tells if all controls are disabled
// Tells if the given control is considered enabled, taking global flag into account
inline bool IsGUIEnabled(AGS::Shared::GUIObject *g) {
return (_G(all_buttons_disabled) < 0) && g->IsEnabled();
}
} // namespace AGS3
#endif

View File

@@ -0,0 +1,284 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/std/algorithm.h"
#include "ags/shared/ac/sprite_cache.h"
#include "ags/shared/gui/gui_main.h"
#include "ags/shared/gui/gui_slider.h"
#include "ags/shared/util/stream.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
GUISlider::GUISlider() {
MinValue = 0;
MaxValue = 10;
Value = 0;
BgImage = 0;
HandleImage = 0;
HandleOffset = 0;
IsMousePressed = false;
_scEventCount = 1;
_scEventNames[0] = "Change";
_scEventArgs[0] = "GUIControl *control";
_handleRange = 0;
}
bool GUISlider::IsHorizontal() const {
return _width > _height;
}
bool GUISlider::HasAlphaChannel() const {
return is_sprite_alpha(BgImage) || is_sprite_alpha(HandleImage);
}
bool GUISlider::IsOverControl(int x, int y, int leeway) const {
// check the overall boundary
if (GUIObject::IsOverControl(x, y, leeway))
return true;
// now check the handle too
return _cachedHandle.IsInside(Point(x - X, y - Y));
}
Rect GUISlider::CalcGraphicRect(bool /*clipped*/) {
// Sliders are never clipped as of 3.6.0
// TODO: precalculate everything on width/height/graphic change!!
UpdateMetrics();
Rect logical = RectWH(0, 0, _width, _height);
Rect bar = _cachedBar;
Rect handle = _cachedHandle;
return Rect(
MIN(MIN(logical.Left, bar.Left), handle.Left),
MIN(MIN(logical.Top, bar.Top), handle.Top),
MAX(MAX(logical.Right, bar.Right), handle.Right),
MAX(MAX(logical.Bottom, bar.Bottom), handle.Bottom)
);
}
void GUISlider::UpdateMetrics() {
// Clamp Value
// TODO: this is necessary here because some Slider fields are still public
if (MinValue >= MaxValue)
MaxValue = MinValue + 1;
Value = Math::Clamp(Value, MinValue, MaxValue);
// Test if sprite is available; // TODO: return a placeholder from spriteset instead!
const int handle_im = ((HandleImage > 0) && _GP(spriteset).DoesSpriteExist(HandleImage)) ? HandleImage : 0;
// Depending on slider's orientation, thickness is either Height or Width
const int thickness = IsHorizontal() ? _height : _width;
// "thick_f" is the factor for calculating relative element positions
const int thick_f = thickness / 3; // one third of the control's thickness
// Bar thickness
const int bar_thick = thick_f * 2 + 2;
// Calculate handle size
Size handle_sz;
if (handle_im > 0) // handle is a sprite
{
handle_sz = Size(get_adjusted_spritewidth(handle_im),
get_adjusted_spriteheight(handle_im));
} else // handle is a drawn rectangle
{
if (IsHorizontal())
handle_sz = Size(get_fixed_pixel_size(4) + 1, bar_thick + (thick_f - 1) * 2);
else
handle_sz = Size(bar_thick + (thick_f - 1) * 2, get_fixed_pixel_size(4) + 1);
}
// Calculate bar and handle positions
Rect bar;
Rect handle;
int handle_range;
if (IsHorizontal()) // horizontal slider
{
// Value pos is a coordinate corresponding to current slider's value
bar = RectWH(1, _height / 2 - thick_f, _width - 1, bar_thick);
handle_range = _width - 4;
int value_pos = (int)(((float)(Value - MinValue) * (float)handle_range) / (float)(MaxValue - MinValue));
handle = RectWH((bar.Left + get_fixed_pixel_size(2)) - (handle_sz.Width / 2) + 1 + value_pos - 2,
bar.Top + (bar.GetHeight() - handle_sz.Height) / 2,
handle_sz.Width, handle_sz.Height);
handle.MoveToY(handle.Top + data_to_game_coord(HandleOffset));
}
// vertical slider
else {
bar = RectWH(_width / 2 - thick_f, 1, bar_thick, _height - 1);
handle_range = _height - 4;
int value_pos = (int)(((float)(MaxValue - Value) * (float)handle_range) / (float)(MaxValue - MinValue));
handle = RectWH(bar.Left + (bar.GetWidth() - handle_sz.Width) / 2,
(bar.Top + get_fixed_pixel_size(2)) - (handle_sz.Height / 2) + 1 + value_pos - 2,
handle_sz.Width, handle_sz.Height);
handle.MoveToX(handle.Left + data_to_game_coord(HandleOffset));
}
_cachedBar = bar;
_cachedHandle = handle;
_handleRange = MAX(1, handle_range);
}
void GUISlider::Draw(Bitmap *ds, int x, int y) {
UpdateMetrics();
Rect bar = Rect::MoveBy(_cachedBar, x, y);
Rect handle = Rect::MoveBy(_cachedHandle, x, y);
color_t draw_color;
if (BgImage > 0) {
// tiled image as slider background
int x_inc = 0;
int y_inc = 0;
if (IsHorizontal()) {
x_inc = get_adjusted_spritewidth(BgImage);
// centre the image vertically
bar.Top = y + (_height / 2) - get_adjusted_spriteheight(BgImage) / 2;
} else {
y_inc = get_adjusted_spriteheight(BgImage);
// centre the image horizontally
bar.Left = x + (_width / 2) - get_adjusted_spritewidth(BgImage) / 2;
}
int cx = bar.Left;
int cy = bar.Top;
// draw the tiled background image
do {
draw_gui_sprite(ds, BgImage, cx, cy, true);
cx += x_inc;
cy += y_inc;
// done as a do..while so that at least one of the image is drawn
} while ((cx + x_inc <= bar.Right) && (cy + y_inc <= bar.Bottom));
} else {
// normal grey background
draw_color = ds->GetCompatibleColor(16);
ds->FillRect(bar, draw_color);
draw_color = ds->GetCompatibleColor(8);
ds->DrawLine(Line(bar.Left, bar.Top, bar.Left, bar.Bottom), draw_color);
ds->DrawLine(Line(bar.Left, bar.Top, bar.Right, bar.Top), draw_color);
draw_color = ds->GetCompatibleColor(15);
ds->DrawLine(Line(bar.Right, bar.Top + 1, bar.Right, bar.Bottom), draw_color);
ds->DrawLine(Line(bar.Left, bar.Bottom, bar.Right, bar.Bottom), draw_color);
}
// Test if sprite is available; // TODO: return a placeholder from spriteset instead!
const int handle_im = ((HandleImage > 0) && _GP(spriteset).DoesSpriteExist(HandleImage)) ? HandleImage : 0;
if (handle_im > 0) // handle is a sprite
{
draw_gui_sprite(ds, handle_im, handle.Left, handle.Top, true);
} else // handle is a drawn rectangle
{
// normal grey tracker handle
draw_color = ds->GetCompatibleColor(7);
ds->FillRect(handle, draw_color);
draw_color = ds->GetCompatibleColor(15);
ds->DrawLine(Line(handle.Left, handle.Top, handle.Right, handle.Top), draw_color);
ds->DrawLine(Line(handle.Left, handle.Top, handle.Left, handle.Bottom), draw_color);
draw_color = ds->GetCompatibleColor(16);
ds->DrawLine(Line(handle.Right, handle.Top + 1, handle.Right, handle.Bottom), draw_color);
ds->DrawLine(Line(handle.Left + 1, handle.Bottom, handle.Right, handle.Bottom), draw_color);
}
}
bool GUISlider::OnMouseDown() {
IsMousePressed = true;
// lock focus to ourselves
return true;
}
void GUISlider::OnMouseMove(int x, int y) {
if (!IsMousePressed)
return;
int32_t value;
assert(_handleRange > 0);
if (IsHorizontal())
value = (int)(((float)((x - X) - 2) * (float)(MaxValue - MinValue)) / (float)_handleRange) + MinValue;
else
value = (int)(((float)(((Y + _height) - y) - 2) * (float)(MaxValue - MinValue)) / (float)_handleRange) + MinValue;
value = Math::Clamp(value, MinValue, MaxValue);
if (value != Value) {
Value = value;
MarkChanged();
}
IsActivated = true;
}
void GUISlider::OnMouseUp() {
IsMousePressed = false;
}
void GUISlider::ReadFromFile(Stream *in, GuiVersion gui_version) {
GUIObject::ReadFromFile(in, gui_version);
MinValue = in->ReadInt32();
MaxValue = in->ReadInt32();
Value = in->ReadInt32();
if (gui_version < kGuiVersion_350) { // NOTE: reading into actual variables only for old savegame support
IsMousePressed = in->ReadInt32() != 0;
}
if (gui_version >= kGuiVersion_unkn_104) {
HandleImage = in->ReadInt32();
HandleOffset = in->ReadInt32();
BgImage = in->ReadInt32();
} else {
HandleImage = -1;
HandleOffset = 0;
BgImage = 0;
}
UpdateMetrics();
}
void GUISlider::WriteToFile(Stream *out) const {
GUIObject::WriteToFile(out);
out->WriteInt32(MinValue);
out->WriteInt32(MaxValue);
out->WriteInt32(Value);
out->WriteInt32(HandleImage);
out->WriteInt32(HandleOffset);
out->WriteInt32(BgImage);
}
void GUISlider::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) {
GUIObject::ReadFromSavegame(in, svg_ver);
BgImage = in->ReadInt32();
HandleImage = in->ReadInt32();
HandleOffset = in->ReadInt32();
MinValue = in->ReadInt32();
MaxValue = in->ReadInt32();
Value = in->ReadInt32();
UpdateMetrics();
}
void GUISlider::WriteToSavegame(Stream *out) const {
GUIObject::WriteToSavegame(out);
out->WriteInt32(BgImage);
out->WriteInt32(HandleImage);
out->WriteInt32(HandleOffset);
out->WriteInt32(MinValue);
out->WriteInt32(MaxValue);
out->WriteInt32(Value);
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,85 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_GUI_GUI_SLIDER_H
#define AGS_SHARED_GUI_GUI_SLIDER_H
#include "common/std/vector.h"
#include "ags/shared/gui/gui_object.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class GUISlider : public GUIObject {
public:
GUISlider();
// Tells if the slider is horizontal (otherwise - vertical)
bool IsHorizontal() const;
bool IsOverControl(int x, int y, int leeway) const override;
// Compatibility: sliders are not clipped as of 3.6.0
bool IsContentClipped() const override { return false; }
bool HasAlphaChannel() const override;
// Operations
Rect CalcGraphicRect(bool clipped) override;
void Draw(Bitmap *ds, int x = 0, int y = 0) override;
// Events
bool OnMouseDown() override;
void OnMouseMove(int xp, int yp) override;
void OnMouseUp() override;
// Serialization
void ReadFromFile(Stream *in, GuiVersion gui_version) override;
void WriteToFile(Stream *out) const override;
void ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) override;
void WriteToSavegame(Stream *out) const override;
// TODO: these members are currently public; hide them later
public:
int32_t MinValue;
int32_t MaxValue;
int32_t Value;
int32_t BgImage;
int32_t HandleImage;
int32_t HandleOffset;
bool IsMousePressed;
private:
// Updates dynamic metrics and positions of elements
void UpdateMetrics();
// Cached coordinates of slider bar; in relative coords
Rect _cachedBar;
// Cached coordinates of slider handle; in relative coords
Rect _cachedHandle;
// The length of the handle movement range, in pixels
int _handleRange;
};
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,177 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/ac/keycode.h"
#include "ags/shared/font/fonts.h"
#include "ags/shared/gui/gui_main.h"
#include "ags/shared/gui/gui_textbox.h"
#include "ags/shared/util/stream.h"
#include "ags/shared/util/string_utils.h"
namespace AGS3 {
#define GUITEXTBOX_LEGACY_TEXTLEN 200
namespace AGS {
namespace Shared {
GUITextBox::GUITextBox() {
Font = 0;
TextColor = 0;
TextBoxFlags = kTextBox_DefFlags;
_scEventCount = 1;
_scEventNames[0] = "Activate";
_scEventArgs[0] = "GUIControl *control";
}
bool GUITextBox::HasAlphaChannel() const {
return is_font_antialiased(Font);
}
bool GUITextBox::IsBorderShown() const {
return (TextBoxFlags & kTextBox_ShowBorder) != 0;
}
Rect GUITextBox::CalcGraphicRect(bool clipped) {
if (clipped)
return RectWH(0, 0, _width, _height);
// TODO: need to find a way to cache text position, or there'll be some repetition
Rect rc = RectWH(0, 0, _width, _height);
Point text_at(1 + get_fixed_pixel_size(1), 1 + get_fixed_pixel_size(1));
Rect text_rc = GUI::CalcTextGraphicalRect(Text.GetCStr(), Font, text_at);
if (IsGUIEnabled(this)) {
// add a cursor
Rect cur_rc = RectWH(
text_rc.Right + 3,
1 + get_font_height(Font),
get_fixed_pixel_size(5),
get_fixed_pixel_size(1) - 1);
text_rc = SumRects(text_rc, cur_rc);
}
return SumRects(rc, text_rc);
}
void GUITextBox::Draw(Bitmap *ds, int x, int y) {
color_t text_color = ds->GetCompatibleColor(TextColor);
color_t draw_color = ds->GetCompatibleColor(TextColor);
if (IsBorderShown()) {
ds->DrawRect(RectWH(x, y, _width, _height), draw_color);
if (get_fixed_pixel_size(1) > 1) {
ds->DrawRect(Rect(x + 1, y + 1, x + _width - get_fixed_pixel_size(1), y + _height - get_fixed_pixel_size(1)), draw_color);
}
}
DrawTextBoxContents(ds, x, y, text_color);
}
// TODO: a shared utility function
static void Backspace(String &text) {
if (get_uformat() == U_UTF8) {// Find where the last utf8 char begins
const char *ptr_end = text.GetCStr() + text.GetLength();
const char *ptr = ptr_end - 1;
for (; ptr > text.GetCStr() && ((*ptr & 0xC0) == 0x80); --ptr);
text.ClipRight(ptr_end - ptr);
} else {
text.ClipRight(1);
}
}
void GUITextBox::OnKeyPress(const KeyInput &ki) {
switch (ki.Key) {
case eAGSKeyCodeReturn:
IsActivated = true;
return;
case eAGSKeyCodeBackspace:
Backspace(Text);
MarkChanged();
return;
default: break;
}
if (ki.UChar == 0)
return; // not a textual event
if (get_uformat() == U_UTF8)
Text.Append(ki.Text); // proper unicode char
else if (ki.UChar < 256)
Text.AppendChar(static_cast<uint8_t>(ki.UChar)); // ascii/ansi-range char in ascii mode
else
return; // char from an unsupported range, don't print but still report as handled
// if the new string is too long, remove the new character
if (get_text_width(Text.GetCStr(), Font) > (_width - (6 + get_fixed_pixel_size(5))))
Backspace(Text);
MarkChanged();
}
void GUITextBox::SetShowBorder(bool on) {
if (on)
TextBoxFlags |= kTextBox_ShowBorder;
else
TextBoxFlags &= ~kTextBox_ShowBorder;
}
// TODO: replace string serialization with StrUtil::ReadString and WriteString
// methods in the future, to keep this organized.
void GUITextBox::WriteToFile(Stream *out) const {
GUIObject::WriteToFile(out);
StrUtil::WriteString(Text, out);
out->WriteInt32(Font);
out->WriteInt32(TextColor);
out->WriteInt32(TextBoxFlags);
}
void GUITextBox::ReadFromFile(Stream *in, GuiVersion gui_version) {
GUIObject::ReadFromFile(in, gui_version);
if (gui_version < kGuiVersion_350)
Text.ReadCount(in, GUITEXTBOX_LEGACY_TEXTLEN);
else
Text = StrUtil::ReadString(in);
Font = in->ReadInt32();
TextColor = in->ReadInt32();
TextBoxFlags = in->ReadInt32();
// reverse particular flags from older format
if (gui_version < kGuiVersion_350)
TextBoxFlags ^= kTextBox_OldFmtXorMask;
if (TextColor == 0)
TextColor = 16;
}
void GUITextBox::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) {
GUIObject::ReadFromSavegame(in, svg_ver);
Font = in->ReadInt32();
TextColor = in->ReadInt32();
Text = StrUtil::ReadString(in);
if (svg_ver >= kGuiSvgVersion_350)
TextBoxFlags = in->ReadInt32();
}
void GUITextBox::WriteToSavegame(Stream *out) const {
GUIObject::WriteToSavegame(out);
out->WriteInt32(Font);
out->WriteInt32(TextColor);
StrUtil::WriteString(Text, out);
out->WriteInt32(TextBoxFlags);
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,71 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_GUI_GUI_TEXTBOX_H
#define AGS_SHARED_GUI_GUI_TEXTBOX_H
#include "common/std/vector.h"
#include "ags/shared/gui/gui_object.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class GUITextBox : public GUIObject {
public:
GUITextBox();
bool HasAlphaChannel() const override;
bool IsBorderShown() const;
// Operations
Rect CalcGraphicRect(bool clipped) override;
void Draw(Bitmap *ds, int x = 0, int y = 0) override;
void SetShowBorder(bool on);
// Events
void OnKeyPress(const KeyInput &ki) override;
// Serialization
void ReadFromFile(Stream *in, GuiVersion gui_version) override;
void WriteToFile(Stream *out) const override;
void ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) override;
void WriteToSavegame(Stream *out) const override;
// TODO: these members are currently public; hide them later
public:
int32_t Font;
String Text;
color_t TextColor;
private:
int32_t TextBoxFlags;
String _textToDraw;
void DrawTextBoxContents(Bitmap *ds, int x, int y, color_t text_color);
};
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,85 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/std/utility.h"
#include "ags/shared/script/cc_common.h"
#include "ags/shared/util/string.h"
#include "ags/globals.h"
namespace AGS3 {
using namespace AGS::Shared;
void ccSetOption(int optbit, int onoroff) {
if (onoroff)
_G(ccCompOptions) |= optbit;
else
_G(ccCompOptions) &= ~optbit;
}
int ccGetOption(int optbit) {
if (_G(ccCompOptions) & optbit)
return 1;
return 0;
}
void cc_clear_error() {
_GP(ccError) = ScriptError();
}
bool cc_has_error() {
return _GP(ccError).HasError;
}
const ScriptError &cc_get_error() {
return _GP(ccError);
}
String cc_get_err_callstack(int max_lines) {
return cc_has_error() ? _GP(ccError).CallStack : cc_get_callstack(max_lines);
}
void cc_error(const char *descr, ...) {
_GP(ccError).IsUserError = false;
if (descr[0] == '!') {
_GP(ccError).IsUserError = true;
descr++;
}
va_list ap;
va_start(ap, descr);
String displbuf = String::FromFormatV(descr, ap);
va_end(ap);
// TODO: because this global ccError is a global shared variable,
// we have to use project-dependent function to format the final message
_GP(ccError).ErrorString = cc_format_error(displbuf);
_GP(ccError).CallStack = cc_get_callstack();
_GP(ccError).HasError = 1;
_GP(ccError).Line = _G(currentline);
}
void cc_error(const ScriptError &err) {
_GP(ccError) = err;
}
} // namespace AGS3

View File

@@ -0,0 +1,67 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// Script options and error reporting.
#ifndef AGS_SHARED_SCRIPT_CC_COMMON_H
#define AGS_SHARED_SCRIPT_CC_COMMON_H
#include "ags/shared/util/string.h"
namespace AGS3 {
#define SCOPT_EXPORTALL 1 // export all functions automatically
#define SCOPT_SHOWWARNINGS 2 // printf warnings to console
#define SCOPT_LINENUMBERS 4 // include line numbers in compiled code
#define SCOPT_AUTOIMPORT 8 // when creating instance, export funcs to other scripts
#define SCOPT_DEBUGRUN 0x10 // write instructions as they are procssed to log file
#define SCOPT_NOIMPORTOVERRIDE 0x20 // do not allow an import to be re-declared
#define SCOPT_LEFTTORIGHT 0x40 // left-to-right operator precedance
#define SCOPT_OLDSTRINGS 0x80 // allow old-style strings
#define SCOPT_UTF8 0x100 // UTF-8 text mode
extern void ccSetOption(int, int);
extern int ccGetOption(int);
// error reporting
struct ScriptError {
bool HasError = false; // set if error occurs
bool IsUserError = false; // marks script use errors
AGS::Shared::String ErrorString; // description of the error
int Line = 0; // line number of the error
AGS::Shared::String CallStack; // callstack where error happened
};
void cc_clear_error();
bool cc_has_error();
const ScriptError &cc_get_error();
// Returns callstack of the last recorded script error, or a callstack
// of a current execution point, if no script error is currently saved in memory.
AGS::Shared::String cc_get_err_callstack(int max_lines = INT_MAX);
void cc_error(const char *, ...);
void cc_error(const ScriptError &err);
// Project-dependent script error formatting
AGS::Shared::String cc_format_error(const AGS::Shared::String &message);
} // namespace AGS3
#endif

View File

@@ -0,0 +1,134 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_SCRIPT_CC_INTERNAL_H
#define AGS_SHARED_SCRIPT_CC_INTERNAL_H
namespace AGS3 {
#define SCOM_VERSION 90
#define SCOM_VERSIONSTR "0.90"
// virtual CPU registers
#define SREG_SP 1 // stack pointer
#define SREG_MAR 2 // memory address register
#define SREG_AX 3 // general purpose
#define SREG_BX 4
#define SREG_CX 5
#define SREG_OP 6 // object pointer for member func calls
#define SREG_DX 7
#define CC_NUM_REGISTERS 8
// virtual CPU commands
#define SCMD_ADD 1 // reg1 += arg2
#define SCMD_SUB 2 // reg1 -= arg2
#define SCMD_REGTOREG 3 // reg2 = reg1
#define SCMD_WRITELIT 4 // m[MAR] = arg2 (copy arg1 bytes)
#define SCMD_RET 5 // return from subroutine
#define SCMD_LITTOREG 6 // set reg1 to literal value arg2
#define SCMD_MEMREAD 7 // reg1 = m[MAR]
#define SCMD_MEMWRITE 8 // m[MAR] = reg1
#define SCMD_MULREG 9 // reg1 *= reg2
#define SCMD_DIVREG 10 // reg1 /= reg2
#define SCMD_ADDREG 11 // reg1 += reg2
#define SCMD_SUBREG 12 // reg1 -= reg2
#define SCMD_BITAND 13 // bitwise reg1 & reg2
#define SCMD_BITOR 14 // bitwise reg1 | reg2
#define SCMD_ISEQUAL 15 // reg1 == reg2 reg1=1 if true, =0 if not
#define SCMD_NOTEQUAL 16 // reg1 != reg2
#define SCMD_GREATER 17 // reg1 > reg2
#define SCMD_LESSTHAN 18 // reg1 < reg2
#define SCMD_GTE 19 // reg1 >= reg2
#define SCMD_LTE 20 // reg1 <= reg2
#define SCMD_AND 21 // (reg1!=0) && (reg2!=0) -> reg1
#define SCMD_OR 22 // (reg1!=0) || (reg2!=0) -> reg1
#define SCMD_CALL 23 // jump to subroutine at reg1
#define SCMD_MEMREADB 24 // reg1 = m[MAR] (1 byte)
#define SCMD_MEMREADW 25 // reg1 = m[MAR] (2 bytes)
#define SCMD_MEMWRITEB 26 // m[MAR] = reg1 (1 byte)
#define SCMD_MEMWRITEW 27 // m[MAR] = reg1 (2 bytes)
#define SCMD_JZ 28 // jump if ax==0 to arg1
#define SCMD_PUSHREG 29 // m[sp]=reg1; sp++
#define SCMD_POPREG 30 // sp--; reg1=m[sp]
#define SCMD_JMP 31 // jump to arg1
#define SCMD_MUL 32 // reg1 *= arg2
#define SCMD_CALLEXT 33 // call external (imported) function reg1
#define SCMD_PUSHREAL 34 // push reg1 onto real stack
#define SCMD_SUBREALSTACK 35 // decrement stack ptr by literal
#define SCMD_LINENUM 36 // debug info - source code line number
#define SCMD_CALLAS 37 // call external script function
#define SCMD_THISBASE 38 // current relative address
#define SCMD_NUMFUNCARGS 39 // number of arguments for ext func call
#define SCMD_MODREG 40 // reg1 %= reg2
#define SCMD_XORREG 41 // reg1 ^= reg2
#define SCMD_NOTREG 42 // reg1 = !reg1
#define SCMD_SHIFTLEFT 43 // reg1 = reg1 << reg2
#define SCMD_SHIFTRIGHT 44 // reg1 = reg1 >> reg2
#define SCMD_CALLOBJ 45 // next call is member function of reg1
#define SCMD_CHECKBOUNDS 46 // check reg1 is between 0 and arg2
#define SCMD_MEMWRITEPTR 47 // m[MAR] = reg1 (adjust ptr addr)
#define SCMD_MEMREADPTR 48 // reg1 = m[MAR] (adjust ptr addr)
#define SCMD_MEMZEROPTR 49 // m[MAR] = 0 (blank ptr)
#define SCMD_MEMINITPTR 50 // m[MAR] = reg1 (but don't free old one)
#define SCMD_LOADSPOFFS 51 // MAR = SP - arg1 (optimization for local var access)
#define SCMD_CHECKNULL 52 // error if MAR==0
#define SCMD_FADD 53 // reg1 += arg2 (float,int)
#define SCMD_FSUB 54 // reg1 -= arg2 (float,int)
#define SCMD_FMULREG 55 // reg1 *= reg2 (float)
#define SCMD_FDIVREG 56 // reg1 /= reg2 (float)
#define SCMD_FADDREG 57 // reg1 += reg2 (float)
#define SCMD_FSUBREG 58 // reg1 -= reg2 (float)
#define SCMD_FGREATER 59 // reg1 > reg2 (float)
#define SCMD_FLESSTHAN 60 // reg1 < reg2 (float)
#define SCMD_FGTE 61 // reg1 >= reg2 (float)
#define SCMD_FLTE 62 // reg1 <= reg2 (float)
#define SCMD_ZEROMEMORY 63 // m[MAR]..m[MAR+(arg1-1)] = 0
#define SCMD_CREATESTRING 64 // reg1 = new String(reg1)
#define SCMD_STRINGSEQUAL 65 // (char*)reg1 == (char*)reg2 reg1=1 if true, =0 if not
#define SCMD_STRINGSNOTEQ 66 // (char*)reg1 != (char*)reg2
#define SCMD_CHECKNULLREG 67 // error if reg1 == NULL
#define SCMD_LOOPCHECKOFF 68 // no loop checking for this function
#define SCMD_MEMZEROPTRND 69 // m[MAR] = 0 (blank ptr, no dispose if = ax)
#define SCMD_JNZ 70 // jump to arg1 if ax!=0
#define SCMD_DYNAMICBOUNDS 71 // check reg1 is between 0 and m[MAR-4]
#define SCMD_NEWARRAY 72 // reg1 = new array of reg1 elements, each of size arg2 (arg3=managed type?)
#define SCMD_NEWUSEROBJECT 73 // reg1 = new user object of arg1 size
#define CC_NUM_SCCMDS 74
#define MAX_SCMD_ARGS 3 // maximal possible number of arguments
#define EXPORT_FUNCTION 1
#define EXPORT_DATA 2
#define FIXUP_NOFIXUP 0 // no-op
#define FIXUP_GLOBALDATA 1 // code[fixup] += &globaldata[0]
#define FIXUP_FUNCTION 2 // code[fixup] += &code[0]
#define FIXUP_STRING 3 // code[fixup] += &strings[0]
#define FIXUP_IMPORT 4 // code[fixup] = &imported_thing[code[fixup]]
#define FIXUP_DATADATA 5 // globaldata[fixup] += &globaldata[0]
#define FIXUP_STACK 6 // code[fixup] += &stack[0]
// Script file signature
#define ENDFILESIG 0xbeefcafe
} // namespace AGS3
#endif

View File

@@ -0,0 +1,336 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/script/cc_common.h"
#include "ags/shared/script/cc_script.h"
#include "ags/shared/script/cc_internal.h"
#include "ags/shared/util/stream.h"
#include "ags/shared/util/string_compat.h"
#include "ags/shared/util/string_utils.h"
#include "ags/globals.h"
namespace AGS3 {
using namespace AGS::Shared;
ccScript *ccScript::CreateFromStream(Stream *in) {
ccScript *scri = new ccScript();
if (!scri->Read(in)) {
delete scri;
return nullptr;
}
return scri;
}
ccScript::ccScript() {
globaldata = nullptr;
globaldatasize = 0;
code = nullptr;
codesize = 0;
strings = nullptr;
stringssize = 0;
fixuptypes = nullptr;
fixups = nullptr;
numfixups = 0;
importsCapacity = 0;
imports = nullptr;
numimports = 0;
exportsCapacity = 0;
exports = nullptr;
export_addr = nullptr;
numexports = 0;
instances = 0;
sectionNames = nullptr;
sectionOffsets = nullptr;
numSections = 0;
capacitySections = 0;
}
ccScript::ccScript(const ccScript &src) {
globaldatasize = src.globaldatasize;
if (globaldatasize > 0) {
globaldata = (char *)malloc(globaldatasize);
memcpy(globaldata, src.globaldata, globaldatasize);
} else {
globaldata = nullptr;
}
codesize = src.codesize;
if (codesize > 0) {
code = (int32_t *)malloc(codesize * sizeof(int32_t));
memcpy(code, src.code, sizeof(int32_t) * codesize);
} else {
code = nullptr;
}
stringssize = src.stringssize;
if (stringssize > 0) {
strings = (char *)malloc(stringssize);
memcpy(strings, src.strings, stringssize);
} else {
strings = nullptr;
}
numfixups = src.numfixups;
if (numfixups > 0) {
fixuptypes = (char *)malloc(numfixups);
fixups = (int32_t *)malloc(numfixups * sizeof(int32_t));
memcpy(fixuptypes, src.fixuptypes, numfixups);
memcpy(fixups, src.fixups, numfixups * sizeof(int32_t));
} else {
fixups = nullptr;
fixuptypes = nullptr;
}
importsCapacity = src.numimports;
numimports = src.numimports;
if (numimports > 0) {
imports = (char **)malloc(sizeof(char *) * numimports);
for (int i = 0; i < numimports; ++i)
imports[i] = ags_strdup(src.imports[i]);
} else {
imports = nullptr;
}
exportsCapacity = src.numexports;
numexports = src.numexports;
if (numexports > 0) {
exports = (char **)malloc(sizeof(char *) * numexports);
export_addr = (int32_t *)malloc(sizeof(int32_t) * numexports);
for (int i = 0; i < numexports; ++i) {
exports[i] = ags_strdup(src.exports[i]);
export_addr[i] = src.export_addr[i];
}
} else {
exports = nullptr;
export_addr = nullptr;
}
capacitySections = src.numSections;
numSections = src.numSections;
if (numSections > 0) {
sectionNames = (char **)malloc(numSections * sizeof(char *));
sectionOffsets = (int32_t *)malloc(numSections * sizeof(int32_t));
for (int i = 0; i < numSections; ++i) {
sectionNames[i] = ags_strdup(src.sectionNames[i]);
sectionOffsets[i] = src.sectionOffsets[i];
}
} else {
numSections = 0;
sectionNames = nullptr;
sectionOffsets = nullptr;
}
instances = 0;
}
ccScript::~ccScript() {
Free();
}
void ccScript::Write(Stream *out) {
int n;
out->Write(_G(scfilesig), 4);
out->WriteInt32(SCOM_VERSION);
out->WriteInt32(globaldatasize);
out->WriteInt32(codesize);
out->WriteInt32(stringssize);
if (globaldatasize > 0)
out->WriteArray(globaldata, globaldatasize, 1);
if (codesize > 0)
out->WriteArrayOfInt32(code, codesize);
if (stringssize > 0)
out->WriteArray(strings, stringssize, 1);
out->WriteInt32(numfixups);
if (numfixups > 0) {
out->WriteArray(fixuptypes, numfixups, 1);
out->WriteArrayOfInt32(fixups, numfixups);
}
out->WriteInt32(numimports);
for (n = 0; n < numimports; n++)
StrUtil::WriteCStr(imports[n], out);
out->WriteInt32(numexports);
for (n = 0; n < numexports; n++) {
StrUtil::WriteCStr(exports[n], out);
out->WriteInt32(export_addr[n]);
}
out->WriteInt32(numSections);
for (n = 0; n < numSections; n++) {
StrUtil::WriteCStr(sectionNames[n], out);
out->WriteInt32(sectionOffsets[n]);
}
out->WriteInt32(ENDFILESIG);
}
bool ccScript::Read(Stream *in) {
instances = 0;
int n;
char gotsig[5];
_G(currentline) = -1;
in->Read(gotsig, 4);
gotsig[4] = 0;
int fileVer = in->ReadInt32();
if ((strcmp(gotsig, _G(scfilesig)) != 0) || (fileVer > SCOM_VERSION)) {
cc_error("file was not written by ccScript::Write or seek position is incorrect");
return false;
}
globaldatasize = in->ReadInt32();
codesize = in->ReadInt32();
stringssize = in->ReadInt32();
if (globaldatasize > 0) {
globaldata = (char *)malloc(globaldatasize);
in->Read(globaldata, globaldatasize);
} else
globaldata = nullptr;
if (codesize > 0) {
code = (int32_t *)malloc(codesize * sizeof(int32_t));
in->ReadArrayOfInt32(code, codesize);
} else
code = nullptr;
if (stringssize > 0) {
strings = (char *)malloc(stringssize);
in->Read(strings, stringssize);
} else
strings = nullptr;
numfixups = in->ReadInt32();
if (numfixups > 0) {
fixuptypes = (char *)malloc(numfixups);
fixups = (int32_t *)malloc(numfixups * sizeof(int32_t));
in->Read(fixuptypes, numfixups);
in->ReadArrayOfInt32(fixups, numfixups);
} else {
fixups = nullptr;
fixuptypes = nullptr;
}
numimports = in->ReadInt32();
imports = (char **)malloc(sizeof(char *) * numimports);
for (n = 0; n < numimports; n++)
imports[n] = StrUtil::ReadMallocCStrOrNull(in);
numexports = in->ReadInt32();
exports = (char **)malloc(sizeof(char *) * numexports);
export_addr = (int32_t *)malloc(sizeof(int32_t) * numexports);
for (n = 0; n < numexports; n++) {
exports[n] = StrUtil::ReadMallocCStrOrNull(in);
export_addr[n] = in->ReadInt32();
}
if (fileVer >= 83) {
// read in the Sections
numSections = in->ReadInt32();
sectionNames = (char **)malloc(numSections * sizeof(char *));
sectionOffsets = (int32_t *)malloc(numSections * sizeof(int32_t));
for (n = 0; n < numSections; n++) {
sectionNames[n] = StrUtil::ReadMallocCStrOrNull(in);
sectionOffsets[n] = in->ReadInt32();
}
} else {
numSections = 0;
sectionNames = nullptr;
sectionOffsets = nullptr;
}
if (static_cast<uint32_t>(in->ReadInt32()) != ENDFILESIG) {
cc_error("internal error rebuilding script");
return false;
}
return true;
}
void ccScript::Free() {
if (globaldata != nullptr)
free(globaldata);
if (code != nullptr)
free(code);
if (strings != nullptr)
free(strings);
if (fixups != nullptr && numfixups > 0)
free(fixups);
if (fixuptypes != nullptr && numfixups > 0)
free(fixuptypes);
globaldata = nullptr;
code = nullptr;
strings = nullptr;
fixups = nullptr;
fixuptypes = nullptr;
int aa;
for (aa = 0; aa < numimports; aa++) {
if (imports[aa] != nullptr)
free(imports[aa]);
}
for (aa = 0; aa < numexports; aa++)
free(exports[aa]);
for (aa = 0; aa < numSections; aa++)
free(sectionNames[aa]);
if (sectionNames != nullptr) {
free(sectionNames);
free(sectionOffsets);
sectionNames = nullptr;
sectionOffsets = nullptr;
}
if (imports != nullptr) {
free(imports);
free(exports);
free(export_addr);
imports = nullptr;
exports = nullptr;
export_addr = nullptr;
}
numimports = 0;
numexports = 0;
numSections = 0;
}
const char *ccScript::GetSectionName(int32_t offs) const {
int i;
for (i = 0; i < numSections; i++) {
if (sectionOffsets[i] < offs)
continue;
break;
}
// if no sections in script, return unknown
if (i == 0)
return "(unknown section)";
return sectionNames[i - 1];
}
} // namespace AGS3

View File

@@ -0,0 +1,85 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_SCRIPT_CC_SCRIPT_H
#define AGS_SHARED_SCRIPT_CC_SCRIPT_H
#include "common/std/memory.h"
#include "ags/shared/core/types.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class Stream;
}
}
using namespace AGS; // FIXME later
struct ccScript {
public:
char *globaldata;
int32_t globaldatasize;
int32_t *code; // executable byte-code, 32-bit per op or arg
int32_t codesize; // TODO: find out if we can make it size_t
char *strings;
int32_t stringssize;
char *fixuptypes; // global data/string area/ etc
int32_t *fixups; // code array index to fixup (in ints)
int numfixups;
int importsCapacity;
char **imports;
int numimports;
int exportsCapacity;
char **exports; // names of exports
int32_t *export_addr; // high byte is type; low 24-bits are offset
int numexports;
int instances;
// 'sections' allow the interpreter to find out which bit
// of the code came from header files, and which from the main file
char **sectionNames;
int32_t *sectionOffsets;
int numSections;
int capacitySections;
static ccScript *CreateFromStream(Shared::Stream *in);
ccScript();
ccScript(const ccScript &src);
virtual ~ccScript(); // there are few derived classes, so dtor should be virtual
// write the script to disk (after compiling)
void Write(Shared::Stream *out);
// read back a script written with Write
bool Read(Shared::Stream *in);
const char *GetSectionName(int32_t offset) const;
protected:
// free the memory occupied by the script - do NOT attempt to run the
// script after calling this function
void Free();
};
typedef std::shared_ptr<ccScript> PScript;
} // namespace AGS3
#endif

View File

@@ -0,0 +1,176 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// Various utility bit and byte operations
//
//=============================================================================
#ifndef AGS_SHARED_UTIL_BBOP_H
#define AGS_SHARED_UTIL_BBOP_H
#include "ags/shared/core/platform.h"
#include "ags/shared/core/types.h"
namespace AGS3 {
#if AGS_PLATFORM_ENDIAN_BIG || defined (TEST_BIGENDIAN)
#define BITBYTE_BIG_ENDIAN
#endif
namespace AGS {
namespace Shared {
enum DataEndianess {
kBigEndian,
kLittleEndian,
#if defined (BITBYTE_BIG_ENDIAN)
kDefaultSystemEndianess = kBigEndian
#else
kDefaultSystemEndianess = kLittleEndian
#endif
};
//
// Various bit flags operations
//
// Converts from one flag into another:
// sets flag2 if flag1 IS set
// TODO: find more optimal way, using bitwise ops?
inline int FlagToFlag(int value, int flag1, int flag2) {
return ((value & flag1) != 0) * flag2;
}
// Sets flag2 if flag1 is NOT set
inline int FlagToNoFlag(int value, int flag1, int flag2) {
return ((value & flag1) == 0) * flag2;
}
namespace BitByteOperations {
struct IntFloatSwap {
union {
float f;
int32_t i32;
} val;
explicit IntFloatSwap(int32_t i) { val.i32 = i; }
explicit IntFloatSwap(float f) { val.f = f; }
};
inline int16_t SwapBytesInt16(const int16_t val) {
return ((val >> 8) & 0xFF) | ((val << 8) & 0xFF00);
}
inline int32_t SwapBytesInt32(const int32_t val) {
return ((val >> 24) & 0xFF) | ((val >> 8) & 0xFF00) | ((val << 8) & 0xFF0000) | ((val << 24) & 0xFF000000);
}
inline int64_t SwapBytesInt64(const int64_t val) {
return ((val >> 56) & 0xFF) | ((val >> 40) & 0xFF00) | ((val >> 24) & 0xFF0000) |
((val >> 8) & 0xFF000000) | ((val << 8) & 0xFF00000000LL) |
((val << 24) & 0xFF0000000000LL) | ((val << 40) & 0xFF000000000000LL) | ((val << 56) & 0xFF00000000000000LL);
}
inline float SwapBytesFloat(const float val) {
IntFloatSwap swap(val);
swap.val.i32 = SwapBytesInt32(swap.val.i32);
return swap.val.f;
}
inline int16_t Int16FromLE(const int16_t val) {
#if defined (BITBYTE_BIG_ENDIAN)
return SwapBytesInt16(val);
#else
return val;
#endif
}
inline int32_t Int32FromLE(const int32_t val) {
#if defined (BITBYTE_BIG_ENDIAN)
return SwapBytesInt32(val);
#else
return val;
#endif
}
inline int64_t Int64FromLE(const int64_t val) {
#if defined (BITBYTE_BIG_ENDIAN)
return SwapBytesInt64(val);
#else
return val;
#endif
}
inline float FloatFromLE(const float val) {
#if defined (BITBYTE_BIG_ENDIAN)
return SwapBytesFloat(val);
#else
return val;
#endif
}
inline int16_t Int16FromBE(const int16_t val) {
#if defined (BITBYTE_BIG_ENDIAN)
return val;
#else
return SwapBytesInt16(val);
#endif
}
inline int32_t Int32FromBE(const int32_t val) {
#if defined (BITBYTE_BIG_ENDIAN)
return val;
#else
return SwapBytesInt32(val);
#endif
}
inline int64_t Int64FromBE(const int64_t val) {
#if defined (BITBYTE_BIG_ENDIAN)
return val;
#else
return SwapBytesInt64(val);
#endif
}
inline float FloatFromBE(const float val) {
#if defined (BITBYTE_BIG_ENDIAN)
return val;
#else
return SwapBytesFloat(val);
#endif
}
} // namespace BitByteOperations
// Aliases for easier calling
namespace BBOp = BitByteOperations;
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

Some files were not shown because too many files have changed in this diff Show More