Initial commit
This commit is contained in:
129
engines/ags/shared/game/custom_properties.cpp
Normal file
129
engines/ags/shared/game/custom_properties.cpp
Normal 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
|
||||
109
engines/ags/shared/game/custom_properties.h
Normal file
109
engines/ags/shared/game/custom_properties.h
Normal 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
|
||||
375
engines/ags/shared/game/interactions.cpp
Normal file
375
engines/ags/shared/game/interactions.cpp
Normal 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
|
||||
213
engines/ags/shared/game/interactions.h
Normal file
213
engines/ags/shared/game/interactions.h
Normal 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
|
||||
943
engines/ags/shared/game/main_game_file.cpp
Normal file
943
engines/ags/shared/game/main_game_file.cpp
Normal 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
|
||||
165
engines/ags/shared/game/main_game_file.h
Normal file
165
engines/ags/shared/game/main_game_file.h
Normal 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
|
||||
54
engines/ags/shared/game/plugin_info.h
Normal file
54
engines/ags/shared/game/plugin_info.h
Normal 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
|
||||
799
engines/ags/shared/game/room_file.cpp
Normal file
799
engines/ags/shared/game/room_file.cpp
Normal 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
|
||||
141
engines/ags/shared/game/room_file.h
Normal file
141
engines/ags/shared/game/room_file.h
Normal 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
|
||||
131
engines/ags/shared/game/room_file_base.cpp
Normal file
131
engines/ags/shared/game/room_file_base.cpp
Normal 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
|
||||
142
engines/ags/shared/game/room_file_deprecated.cpp
Normal file
142
engines/ags/shared/game/room_file_deprecated.cpp
Normal 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
|
||||
286
engines/ags/shared/game/room_struct.cpp
Normal file
286
engines/ags/shared/game/room_struct.cpp
Normal 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
|
||||
398
engines/ags/shared/game/room_struct.h
Normal file
398
engines/ags/shared/game/room_struct.h
Normal 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
|
||||
99
engines/ags/shared/game/room_version.h
Normal file
99
engines/ags/shared/game/room_version.h
Normal 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
|
||||
274
engines/ags/shared/game/tra_file.cpp
Normal file
274
engines/ags/shared/game/tra_file.cpp
Normal 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
|
||||
89
engines/ags/shared/game/tra_file.h
Normal file
89
engines/ags/shared/game/tra_file.h
Normal 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
|
||||
Reference in New Issue
Block a user