826 lines
26 KiB
Go
826 lines
26 KiB
Go
package archive
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
|
|
"github.com/inkyblackness/hacked/ss1/content/archive/level"
|
|
"github.com/inkyblackness/hacked/ss1/content/interpreters"
|
|
"github.com/inkyblackness/hacked/ss1/content/object"
|
|
"github.com/inkyblackness/hacked/ss1/content/text"
|
|
)
|
|
|
|
// GameStateSize specifies the byte count of a the GameState in an archive.
|
|
// Note that in the original archive.dat file, the resource only has 0x054D bytes.
|
|
// This does not matter much, as in the original engine the resource was ignored.
|
|
const GameStateSize = 0x0595
|
|
|
|
const (
|
|
stateHackerNameSize = 20
|
|
// InventoryWeaponSlots is the number of weapons available to the character.
|
|
InventoryWeaponSlots = 7
|
|
weaponSlotsStartOffset = 0x048B
|
|
weaponSlotSize = 5
|
|
|
|
// GrenadeTypeCount is the number of grenades (explosives) available to the character.
|
|
GrenadeTypeCount = 7
|
|
grenadeCountStartOffset = 0x0350
|
|
grenadeTimerStartOffset = 0x04FF
|
|
grenadeTimerDefault = 70
|
|
// GrenadeTimerMaximum is the maximum time that can be set for a grenade in game.
|
|
GrenadeTimerMaximum = 571
|
|
|
|
// PatchTypeCount is the number of patches available to the character.
|
|
PatchTypeCount = 7
|
|
patchCountStartOffset = 0x0349
|
|
|
|
// AmmoTypeCount is the number of types of ammo available to the character.
|
|
AmmoTypeCount = 15
|
|
ammoFullClipCountStartOffset = 0x032B
|
|
ammoExtraRoundsCountStartOffset = 0x033A
|
|
|
|
// GeneralInventorySlotCount is the number of general inventory slots available.
|
|
GeneralInventorySlotCount = 14
|
|
generalInventorySlotStartOffset = 0x007A
|
|
|
|
// HardwareTypeCount is the number of available hardware.
|
|
HardwareTypeCount = 15
|
|
hardwareVersionStartOffset = 0x0309
|
|
hardwareStatusStartOffset = 0x04AE
|
|
|
|
// SoftwareOffenseTypeCount is the number of available offense software.
|
|
SoftwareOffenseTypeCount = 7
|
|
// SoftwareDefenseTypeCount is the number of available defense software.
|
|
SoftwareDefenseTypeCount = 3
|
|
// SoftwareOneshotTypeCount is the number of available one-shot software.
|
|
SoftwareOneshotTypeCount = 4
|
|
|
|
installedOffenseSoftwareStartOffset = 0x0318
|
|
installedDefenseSoftwareStartOffset = 0x031F
|
|
installedOneshotSoftwareStartOffset = 0x0322
|
|
|
|
engineTicksPerSecond = 280
|
|
secondsPerMinute = 60
|
|
engineTicksPerMinute = secondsPerMinute * engineTicksPerSecond
|
|
cyberspaceMinTime = 90 * engineTicksPerSecond
|
|
cyberspaceMaxTime = 30 * secondsPerMinute * engineTicksPerSecond
|
|
|
|
// EMailCount is the number of available EMail status slots.
|
|
EMailCount = 47
|
|
// FragmentCount is the number of available fragment (data) status slots.
|
|
FragmentCount = 23
|
|
// PossibleLogsPerLevel is the number of logs slots available per level.
|
|
PossibleLogsPerLevel = 16
|
|
// LevelsWithLogs is the number of levels that can hold logs.
|
|
LevelsWithLogs = 14
|
|
// LogCount is the total number of logs available.
|
|
LogCount = PossibleLogsPerLevel * LevelsWithLogs
|
|
|
|
messageStatusReceived byte = 0x80
|
|
messageStatusRead byte = 0x40
|
|
|
|
messageStatusStartOffset = 0x0357
|
|
emailStatusStartOffset = messageStatusStartOffset
|
|
logStatusStartOffset = emailStatusStartOffset + EMailCount
|
|
fragmentStatusStartOffset = logStatusStartOffset + LogCount
|
|
|
|
// BooleanVarCount is the number of available boolean variables.
|
|
BooleanVarCount = 512
|
|
booleanVarStartOffset = 0x00B6
|
|
// IntegerVarCount is the number of available integer variables.
|
|
IntegerVarCount = 64
|
|
integerVarStartOffset = 0x00F6
|
|
)
|
|
|
|
// GameState contains all the necessary information for the current progress of the game.
|
|
// It is a combination of world information, character information, and configuration values.
|
|
type GameState struct {
|
|
*interpreters.Instance
|
|
}
|
|
|
|
// InventoryWeaponSlot describes one entry for weapons in the inventory.
|
|
type InventoryWeaponSlot struct {
|
|
Index int
|
|
State *GameState
|
|
}
|
|
|
|
func freeWeaponSlot() []byte {
|
|
return []byte{0xFF, 0x00, 0x00, 0x00, 0x00}
|
|
}
|
|
|
|
func (slot InventoryWeaponSlot) rawData() []byte {
|
|
if (slot.Index < 0) || (slot.Index >= InventoryWeaponSlots) {
|
|
return freeWeaponSlot()
|
|
}
|
|
startIndex := weaponSlotsStartOffset + (weaponSlotSize * slot.Index)
|
|
return slot.State.Raw()[startIndex : startIndex+weaponSlotSize]
|
|
}
|
|
|
|
// IsInUse returns true if the slot is currently holding a weapon.
|
|
func (slot InventoryWeaponSlot) IsInUse() bool {
|
|
return slot.rawData()[0] != 0xFF
|
|
}
|
|
|
|
// SetFree clears the weapon slot.
|
|
func (slot InventoryWeaponSlot) SetFree() {
|
|
rawData := slot.rawData()
|
|
copy(rawData, freeWeaponSlot())
|
|
}
|
|
|
|
// SetInUse inserts the given weapon in this slot. Other fields are reset to zero.
|
|
func (slot InventoryWeaponSlot) SetInUse(subclass object.Subclass, objType object.Type) {
|
|
rawData := slot.rawData()
|
|
copy(rawData, freeWeaponSlot())
|
|
rawData[0] = byte(subclass)
|
|
rawData[1] = byte(objType)
|
|
}
|
|
|
|
// Triple returns the type information of the weapon in this slot.
|
|
func (slot InventoryWeaponSlot) Triple() object.Triple {
|
|
rawData := slot.rawData()
|
|
return object.TripleFrom(int(object.ClassGun), int(rawData[0]), int(rawData[1]))
|
|
}
|
|
|
|
// WeaponState returns the two state values. Their interpretation is subclass specific.
|
|
func (slot InventoryWeaponSlot) WeaponState() (byte, byte) {
|
|
rawData := slot.rawData()
|
|
return rawData[2], rawData[3]
|
|
}
|
|
|
|
// SetWeaponState updates the two state values.
|
|
func (slot InventoryWeaponSlot) SetWeaponState(a, b byte) {
|
|
rawData := slot.rawData()
|
|
rawData[2] = a
|
|
rawData[3] = b
|
|
}
|
|
|
|
// InventoryGrenade describes properties of grenades by type.
|
|
type InventoryGrenade struct {
|
|
Index int
|
|
State *GameState
|
|
}
|
|
|
|
func (grenade InventoryGrenade) isValid() bool {
|
|
return (grenade.Index >= 0) && (grenade.Index < GrenadeTypeCount) && (grenade.State != nil)
|
|
}
|
|
|
|
// Count returns how many explosives the character is carrying.
|
|
func (grenade InventoryGrenade) Count() int {
|
|
if !grenade.isValid() {
|
|
return 0
|
|
}
|
|
return int(grenade.State.Raw()[grenadeCountStartOffset+grenade.Index])
|
|
}
|
|
|
|
// SetCount updates the amount of explosives the character is carrying. Invalid values are clamped.
|
|
func (grenade InventoryGrenade) SetCount(value int) {
|
|
if !grenade.isValid() {
|
|
return
|
|
}
|
|
count := byte(value)
|
|
if value < 0 {
|
|
count = 0
|
|
} else if value > math.MaxUint8 {
|
|
count = math.MaxUint8
|
|
}
|
|
|
|
grenade.State.Raw()[grenadeCountStartOffset+grenade.Index] = count
|
|
}
|
|
|
|
// GrenadeTimerSetting is the value for explosive timers.
|
|
type GrenadeTimerSetting uint16
|
|
|
|
// InSeconds returns the amount in unit of seconds.
|
|
func (setting GrenadeTimerSetting) InSeconds() float32 {
|
|
return float32(setting) / 10.0
|
|
}
|
|
|
|
func (grenade InventoryGrenade) rawTimer() []byte {
|
|
if !grenade.isValid() {
|
|
return []byte{0x00, 0x00}
|
|
}
|
|
startIndex := grenadeTimerStartOffset + (2 * grenade.Index)
|
|
return grenade.State.Raw()[startIndex : startIndex+2]
|
|
}
|
|
|
|
// TimerSetting returns the generic timer used for this type of grenade.
|
|
func (grenade InventoryGrenade) TimerSetting() GrenadeTimerSetting {
|
|
raw := grenade.rawTimer()
|
|
return GrenadeTimerSetting((uint16(raw[1]) << 8) | (uint16(raw[0]) << 0))
|
|
}
|
|
|
|
// SetTimerSetting updates the generic timer used for this type of grenade.
|
|
func (grenade InventoryGrenade) SetTimerSetting(value GrenadeTimerSetting) {
|
|
raw := grenade.rawTimer()
|
|
raw[0] = byte(value >> 0)
|
|
raw[1] = byte(value >> 8)
|
|
}
|
|
|
|
// PatchState describes properties of patches by type.
|
|
type PatchState struct {
|
|
Index int
|
|
State *GameState
|
|
}
|
|
|
|
func (patch PatchState) isValid() bool {
|
|
return (patch.Index >= 0) && (patch.Index < PatchTypeCount) && (patch.State != nil)
|
|
}
|
|
|
|
// Count returns how many patches the character is carrying.
|
|
func (patch PatchState) Count() int {
|
|
if !patch.isValid() {
|
|
return 0
|
|
}
|
|
return int(patch.State.Raw()[patchCountStartOffset+patch.Index])
|
|
}
|
|
|
|
// SetCount updates the amount of patches the character is carrying. Invalid values are clamped.
|
|
func (patch PatchState) SetCount(value int) {
|
|
if !patch.isValid() {
|
|
return
|
|
}
|
|
count := byte(value)
|
|
if value < 0 {
|
|
count = 0
|
|
} else if value > math.MaxUint8 {
|
|
count = math.MaxUint8
|
|
}
|
|
|
|
patch.State.Raw()[patchCountStartOffset+patch.Index] = count
|
|
}
|
|
|
|
// InventoryAmmo describes properties of ammo by type.
|
|
type InventoryAmmo struct {
|
|
Index int
|
|
State *GameState
|
|
}
|
|
|
|
func (ammo InventoryAmmo) isValid() bool {
|
|
return (ammo.Index >= 0) && (ammo.Index < AmmoTypeCount) && (ammo.State != nil)
|
|
}
|
|
|
|
// FullClipCount returns how many full clips the character is carrying.
|
|
func (ammo InventoryAmmo) FullClipCount() int {
|
|
if !ammo.isValid() {
|
|
return 0
|
|
}
|
|
return int(ammo.State.Raw()[ammoFullClipCountStartOffset+ammo.Index])
|
|
}
|
|
|
|
// SetFullClipCount updates the amount of full clips the character is carrying. Invalid values are clamped.
|
|
func (ammo InventoryAmmo) SetFullClipCount(value int) {
|
|
if !ammo.isValid() {
|
|
return
|
|
}
|
|
count := byte(value)
|
|
if value < 0 {
|
|
count = 0
|
|
} else if value > math.MaxUint8 {
|
|
count = math.MaxUint8
|
|
}
|
|
|
|
ammo.State.Raw()[ammoFullClipCountStartOffset+ammo.Index] = count
|
|
}
|
|
|
|
// ExtraRoundsCount returns how many extra rounds the character is carrying.
|
|
func (ammo InventoryAmmo) ExtraRoundsCount() int {
|
|
if !ammo.isValid() {
|
|
return 0
|
|
}
|
|
return int(ammo.State.Raw()[ammoExtraRoundsCountStartOffset+ammo.Index])
|
|
}
|
|
|
|
// SetExtraRoundsCount updates the amount of extra rounds the character is carrying. Invalid values are clamped.
|
|
func (ammo InventoryAmmo) SetExtraRoundsCount(value int) {
|
|
if !ammo.isValid() {
|
|
return
|
|
}
|
|
count := byte(value)
|
|
if value < 0 {
|
|
count = 0
|
|
} else if value > math.MaxUint8 {
|
|
count = math.MaxUint8
|
|
}
|
|
|
|
ammo.State.Raw()[ammoExtraRoundsCountStartOffset+ammo.Index] = count
|
|
}
|
|
|
|
// GeneralInventorySlot describes properties of a general inventory slot.
|
|
type GeneralInventorySlot struct {
|
|
Index int
|
|
State *GameState
|
|
}
|
|
|
|
func (slot GeneralInventorySlot) isValid() bool {
|
|
return (slot.Index >= 0) && (slot.Index < GeneralInventorySlotCount) && (slot.State != nil)
|
|
}
|
|
|
|
func (slot GeneralInventorySlot) rawID() []byte {
|
|
if !slot.isValid() {
|
|
return []byte{0x00, 0x00}
|
|
}
|
|
startIndex := generalInventorySlotStartOffset + (2 * slot.Index)
|
|
return slot.State.Raw()[startIndex : startIndex+2]
|
|
}
|
|
|
|
// ObjectID returns the ID of the object in this inventory slot.
|
|
func (slot GeneralInventorySlot) ObjectID() level.ObjectID {
|
|
raw := slot.rawID()
|
|
return level.ObjectID((uint16(raw[1]) << 8) | (uint16(raw[0]) << 0))
|
|
}
|
|
|
|
// SetObjectID updates the ID in this inventory slot.
|
|
func (slot GeneralInventorySlot) SetObjectID(id level.ObjectID) {
|
|
raw := slot.rawID()
|
|
raw[0] = byte(id >> 0)
|
|
raw[1] = byte(id >> 8)
|
|
}
|
|
|
|
// HardwareState describes properties of hardware by type.
|
|
type HardwareState struct {
|
|
Index int
|
|
State *GameState
|
|
}
|
|
|
|
func (hardware HardwareState) isValid() bool {
|
|
return (hardware.Index >= 0) && (hardware.Index < HardwareTypeCount) && (hardware.State != nil)
|
|
}
|
|
|
|
// Version returns which version of the hardware is installed. Zero means not installed.
|
|
func (hardware HardwareState) Version() int {
|
|
if !hardware.isValid() {
|
|
return 0
|
|
}
|
|
return int(hardware.State.Raw()[hardwareVersionStartOffset+hardware.Index])
|
|
}
|
|
|
|
// SetVersion updates the version the character has installed.
|
|
func (hardware HardwareState) SetVersion(value int) {
|
|
if !hardware.isValid() {
|
|
return
|
|
}
|
|
count := byte(value)
|
|
if value < 0 {
|
|
count = 0
|
|
} else if value > math.MaxUint8 {
|
|
count = math.MaxUint8
|
|
}
|
|
|
|
hardware.State.Raw()[hardwareVersionStartOffset+hardware.Index] = count
|
|
}
|
|
|
|
// IsActive returns whether the hardware is currently active.
|
|
func (hardware HardwareState) IsActive() bool {
|
|
if !hardware.isValid() {
|
|
return false
|
|
}
|
|
return hardware.State.Raw()[hardwareStatusStartOffset+hardware.Index] != 0
|
|
}
|
|
|
|
// SetActive updates the active state of a hardware.
|
|
func (hardware HardwareState) SetActive(on bool) {
|
|
if !hardware.isValid() {
|
|
return
|
|
}
|
|
var newValue byte
|
|
if on {
|
|
newValue = 1
|
|
}
|
|
|
|
hardware.State.Raw()[hardwareStatusStartOffset+hardware.Index] = newValue
|
|
}
|
|
|
|
// VersionedSoftwareState describes software that is upgradeable (exists only once).
|
|
type VersionedSoftwareState struct {
|
|
startOffset int
|
|
limit int
|
|
|
|
Index int
|
|
State *GameState
|
|
}
|
|
|
|
func (sw VersionedSoftwareState) isValid() bool {
|
|
return (sw.Index >= 0) && (sw.Index < sw.limit) && (sw.State != nil)
|
|
}
|
|
|
|
// Version returns the installed version number of the software. Zero means not installed.
|
|
func (sw VersionedSoftwareState) Version() int {
|
|
if !sw.isValid() {
|
|
return 0
|
|
}
|
|
return int(sw.State.Raw()[sw.startOffset+sw.Index])
|
|
}
|
|
|
|
// SetVersion sets the installed version number of the software.
|
|
func (sw VersionedSoftwareState) SetVersion(value int) {
|
|
if !sw.isValid() {
|
|
return
|
|
}
|
|
count := byte(value)
|
|
if value < 0 {
|
|
count = 0
|
|
} else if value > math.MaxUint8 {
|
|
count = math.MaxUint8
|
|
}
|
|
sw.State.Raw()[sw.startOffset+sw.Index] = count
|
|
}
|
|
|
|
// CountedSoftwareState describes software that is expended upon use.
|
|
type CountedSoftwareState struct {
|
|
startOffset int
|
|
limit int
|
|
|
|
Index int
|
|
State *GameState
|
|
}
|
|
|
|
func (sw CountedSoftwareState) isValid() bool {
|
|
return (sw.Index >= 0) && (sw.Index < sw.limit) && (sw.State != nil)
|
|
}
|
|
|
|
// Count returns the available amount of the software.
|
|
func (sw CountedSoftwareState) Count() int {
|
|
if !sw.isValid() {
|
|
return 0
|
|
}
|
|
return int(sw.State.Raw()[sw.startOffset+sw.Index])
|
|
}
|
|
|
|
// SetCount sets the installed amount of the software.
|
|
func (sw CountedSoftwareState) SetCount(value int) {
|
|
if !sw.isValid() {
|
|
return
|
|
}
|
|
count := byte(value)
|
|
if value < 0 {
|
|
count = 0
|
|
} else if value > math.MaxUint8 {
|
|
count = math.MaxUint8
|
|
}
|
|
sw.State.Raw()[sw.startOffset+sw.Index] = count
|
|
}
|
|
|
|
// MessageState describes properties of message by index.
|
|
type MessageState struct {
|
|
startOffset int
|
|
limit int
|
|
|
|
Index int
|
|
State *GameState
|
|
}
|
|
|
|
func (message MessageState) isValid() bool {
|
|
return (message.Index >= 0) && (message.Index < message.limit) && (message.State != nil)
|
|
}
|
|
|
|
// Received returns whether the given message was received.
|
|
func (message MessageState) Received() bool {
|
|
return message.hasFlag(messageStatusReceived)
|
|
}
|
|
|
|
// SetReceived marks the receive status of the message.
|
|
func (message MessageState) SetReceived(value bool) {
|
|
message.setFlag(messageStatusReceived, value)
|
|
}
|
|
|
|
// Read returns whether the given message was read.
|
|
func (message MessageState) Read() bool {
|
|
return message.hasFlag(messageStatusRead)
|
|
}
|
|
|
|
// SetRead marks the read status of the message.
|
|
func (message MessageState) SetRead(value bool) {
|
|
message.setFlag(messageStatusRead, value)
|
|
}
|
|
|
|
func (message MessageState) hasFlag(flag byte) bool {
|
|
if !message.isValid() {
|
|
return false
|
|
}
|
|
return (message.State.Raw()[message.startOffset+message.Index] & flag) != 0
|
|
}
|
|
|
|
func (message MessageState) setFlag(flag byte, on bool) {
|
|
if !message.isValid() {
|
|
return
|
|
}
|
|
offset := message.startOffset + message.Index
|
|
newState := message.State.Raw()[offset]
|
|
newState &= ^flag
|
|
if on {
|
|
newState |= flag
|
|
}
|
|
message.State.Raw()[offset] = newState
|
|
}
|
|
|
|
var gameStateDesc = interpreters.New().
|
|
With("Difficulty: Combat", 0x0015, 1).As(interpreters.RangedValue(0, 3)).
|
|
With("Difficulty: Mission", 0x0016, 1).As(interpreters.RangedValue(0, 3)).
|
|
With("Difficulty: Puzzle", 0x0017, 1).As(interpreters.RangedValue(0, 3)).
|
|
With("Difficulty: Cyber", 0x0018, 1).As(interpreters.RangedValue(0, 3)).
|
|
With("Game time", 0x001C, 4).As(interpreters.SpecialValue("Internal")).
|
|
With("Current Level", 0x0039, 1).As(interpreters.RangedValue(0, 15)).
|
|
With("Health", 0x009C, 1).As(interpreters.FormattedRangedValue(0, 255,
|
|
func(value int) string {
|
|
return fmt.Sprintf("%.2f%%%%", float64(value*100)/255.0)
|
|
})).
|
|
With("Cyberspace integrity", 0x009D, 1).As(interpreters.FormattedRangedValue(0, 255,
|
|
func(value int) string {
|
|
return fmt.Sprintf("%.2f%%%%", float64(value*100)/255.0)
|
|
})).
|
|
With("Health regeneration per minute", 0x009E, 2).As(interpreters.RangedValue(0, 1000)).
|
|
With("Power", 0x00AC, 1).As(interpreters.FormattedRangedValue(0, 255,
|
|
func(value int) string {
|
|
return fmt.Sprintf("%.2f%%%%", float64(value*100)/255.0)
|
|
})).
|
|
With("Power Usage", 0x00AD, 1).As(interpreters.RangedValue(0, 0xFF)).
|
|
With("Out-of-power flag", 0x00AF, 1).As(interpreters.EnumValue(
|
|
map[uint32]string{
|
|
0: "Powered",
|
|
1: "Out-of-power",
|
|
})).
|
|
With("Cyberspace base time", 0x00B2, 4).As(interpreters.FormattedRangedValue(cyberspaceMinTime, cyberspaceMaxTime,
|
|
func(value int) string {
|
|
minutes := value / engineTicksPerMinute
|
|
return fmt.Sprintf("%2dm %2.2fs", minutes, float64(value-(minutes*engineTicksPerMinute))/(engineTicksPerSecond))
|
|
})).
|
|
With("Fatigue regeneration", 0x0181, 2).As(interpreters.RangedValue(0, 400)).
|
|
With("Fatigue regeneration base", 0x0183, 2).As(interpreters.RangedValue(0, 400)).
|
|
With("Fatigue regeneration max", 0x0185, 2).As(interpreters.RangedValue(0, 400)).
|
|
With("Accuracy", 0x0187, 1).As(interpreters.RangedValue(0, 255)).
|
|
With("Shield absorb rate", 0x0188, 1).As(interpreters.RangedValue(0, 255)).
|
|
With("Hacker Position X", 0x053F, 4).As(interpreters.FormattedRangedValue(0x010000, 0x3FFFFF,
|
|
func(value int) string {
|
|
return fmt.Sprintf("%2.03f", float32(value)/0x010000)
|
|
})).
|
|
With("Hacker Position Y", 0x0543, 4).As(interpreters.FormattedRangedValue(0x010000, 0x3FFFFF,
|
|
func(value int) string {
|
|
return fmt.Sprintf("%2.03f", float32(value)/0x010000)
|
|
})).
|
|
With("Hacker Position Z", 0x0547, 4).As(interpreters.FormattedRangedValue(0x010000, 0x1FFFFF,
|
|
func(value int) string {
|
|
return fmt.Sprintf("%2.03f tile(s)", float32(value)/0x010000)
|
|
})).
|
|
With("Hacker Yaw", 0x054B, 4).As(interpreters.RotationValue(0, edmsFullCircle()))
|
|
|
|
func edmsFullCircle() int64 {
|
|
full := math.Pi * 2 * float64(0x10000)
|
|
return int64(full) - 1
|
|
}
|
|
|
|
// NewGameState returns a GameState instance for given raw data.
|
|
func NewGameState(raw []byte) *GameState {
|
|
return &GameState{Instance: gameStateDesc.For(raw)}
|
|
}
|
|
|
|
// IsSavegame returns true if the state describes one during a running game.
|
|
func (state *GameState) IsSavegame() bool {
|
|
// Picking the right property is tricky. Most properties could be pre-initialized
|
|
// by an archive as well.
|
|
// For now, assume the "game time" will not be touched by initial archives.
|
|
// A possible other approach would be to take the "version" field, which
|
|
// is always set when a savegame is created, but is zero in the original. Yet,
|
|
// it is not clear whether or not a (pedantic) engine would respect this field and
|
|
// ignore data when it's zero.
|
|
return state.Get("Game time") > 0
|
|
}
|
|
|
|
// IsDefaulting returns true if the state describes an archive from which the engine
|
|
// should initialize the state for a new game.
|
|
func (state *GameState) IsDefaulting() bool {
|
|
return state.Get("Hacker Position X") == 0
|
|
}
|
|
|
|
// HackerName returns the name interpreted with given codepage.
|
|
func (state *GameState) HackerName(cp text.Codepage) string {
|
|
return cp.Decode(state.Raw()[:stateHackerNameSize])
|
|
}
|
|
|
|
// SetHackerName stores the given name using given codepage, up to the internal available amount of bytes.
|
|
func (state *GameState) SetHackerName(name string, cp text.Codepage) {
|
|
raw := state.Raw()
|
|
var zeroName [stateHackerNameSize]byte
|
|
copy(raw[:stateHackerNameSize], zeroName[:])
|
|
copy(raw[:stateHackerNameSize], cp.Encode(name))
|
|
raw[stateHackerNameSize-1] = 0
|
|
}
|
|
|
|
// CurrentLevel returns the number of the level hacker is currently in.
|
|
func (state GameState) CurrentLevel() int {
|
|
return int(state.Get("Current Level"))
|
|
}
|
|
|
|
// HackerMapPosition returns the rough X/Y location on the map.
|
|
func (state GameState) HackerMapPosition() (level.Coordinate, level.Coordinate) {
|
|
x := state.Get("Hacker Position X")
|
|
y := state.Get("Hacker Position Y")
|
|
return level.CoordinateAt(byte(x>>16), byte(x>>8)), level.CoordinateAt(byte(y>>16), byte(y>>8))
|
|
}
|
|
|
|
// BooleanVar returns the state of the boolean variable at given index. Unsupported indices return 0.
|
|
func (state GameState) BooleanVar(index int) bool {
|
|
if (index < 0) || (index >= BooleanVarCount) {
|
|
return false
|
|
}
|
|
return state.Raw()[booleanVarStartOffset+(index/8)]&(0x01<<(uint(index%8))) != 0
|
|
}
|
|
|
|
// SetBooleanVar sets the state of the boolean variable at given index. Unsupported indices are ignored.
|
|
func (state *GameState) SetBooleanVar(index int, set bool) {
|
|
if (index < 0) || (index >= BooleanVarCount) {
|
|
return
|
|
}
|
|
byteIndex := booleanVarStartOffset + (index / 8)
|
|
bitMask := byte(0x01 << uint(index%8))
|
|
temp := state.Raw()[byteIndex]
|
|
temp &= ^bitMask
|
|
if set {
|
|
temp |= bitMask
|
|
}
|
|
state.Raw()[byteIndex] = temp
|
|
}
|
|
|
|
// IntegerVar returns the value of the integer variable at given index. Unsupported indices return 0.
|
|
func (state *GameState) IntegerVar(index int) int16 {
|
|
if (index < 0) || (index >= IntegerVarCount) {
|
|
return 0
|
|
}
|
|
startOffset := integerVarStartOffset + (2 * index)
|
|
val := uint16(state.Raw()[startOffset+1])<<8 | uint16(state.Raw()[startOffset+0])
|
|
return int16(val)
|
|
}
|
|
|
|
// SetIntegerVar sets the value of the integer variable at given index. Unsupported indices are ignored.
|
|
func (state *GameState) SetIntegerVar(index int, value int16) {
|
|
if (index < 0) || (index >= IntegerVarCount) {
|
|
return
|
|
}
|
|
startOffset := integerVarStartOffset + (2 * index)
|
|
state.Raw()[startOffset+0] = byte((value >> 0) & 0xFF)
|
|
state.Raw()[startOffset+1] = byte((value >> 8) & 0xFF)
|
|
}
|
|
|
|
// InventoryWeaponSlot returns an accessor for the identified weapon slot.
|
|
// Index should be within the range of [0..InventoryWeaponSlots[.
|
|
func (state *GameState) InventoryWeaponSlot(index int) InventoryWeaponSlot {
|
|
return InventoryWeaponSlot{
|
|
Index: index,
|
|
State: state,
|
|
}
|
|
}
|
|
|
|
// InventoryGrenade returns an accessor for the identified grenade type index.
|
|
// Index should be within the range of [0..GrenadeTypeCount[ .
|
|
func (state *GameState) InventoryGrenade(index int) InventoryGrenade {
|
|
return InventoryGrenade{
|
|
Index: index,
|
|
State: state,
|
|
}
|
|
}
|
|
|
|
// PatchState returns an accessor for the identified patch type index.
|
|
// Index should be within the range of [0..PatchTypeCount[ .
|
|
func (state *GameState) PatchState(index int) PatchState {
|
|
return PatchState{
|
|
Index: index,
|
|
State: state,
|
|
}
|
|
}
|
|
|
|
// InventoryAmmo returns an accessor for the identified ammo type index.
|
|
// Index should be within the range of [0..AmmoTypeCount[ .
|
|
func (state *GameState) InventoryAmmo(index int) InventoryAmmo {
|
|
return InventoryAmmo{
|
|
Index: index,
|
|
State: state,
|
|
}
|
|
}
|
|
|
|
// GeneralInventorySlot returns an accessor for the identified general slot.
|
|
// Index should be within the range of [0..GeneralInventorySlotCount[ .
|
|
func (state *GameState) GeneralInventorySlot(index int) GeneralInventorySlot {
|
|
return GeneralInventorySlot{
|
|
Index: index,
|
|
State: state,
|
|
}
|
|
}
|
|
|
|
// HardwareState returns an accessor for the identified hardware type index.
|
|
// Index should be within the range of [0..HardwareTypeCount[ .
|
|
func (state *GameState) HardwareState(index int) HardwareState {
|
|
return HardwareState{
|
|
Index: index,
|
|
State: state,
|
|
}
|
|
}
|
|
|
|
// VersionedOffenseSoftwareState returns an accessor for the identified versioned software status.
|
|
// Index should be within the range of [0..SoftwareOffenseTypeCount[ .
|
|
func (state *GameState) VersionedOffenseSoftwareState(index int) VersionedSoftwareState {
|
|
return VersionedSoftwareState{
|
|
startOffset: installedOffenseSoftwareStartOffset,
|
|
limit: SoftwareOffenseTypeCount,
|
|
Index: index,
|
|
State: state,
|
|
}
|
|
}
|
|
|
|
// VersionedDefenseSoftwareState returns an accessor for the identified versioned software status.
|
|
// Index should be within the range of [0..SoftwareDefenseTypeCount[ .
|
|
func (state *GameState) VersionedDefenseSoftwareState(index int) VersionedSoftwareState {
|
|
return VersionedSoftwareState{
|
|
startOffset: installedDefenseSoftwareStartOffset,
|
|
limit: SoftwareDefenseTypeCount,
|
|
Index: index,
|
|
State: state,
|
|
}
|
|
}
|
|
|
|
// OneshotSoftwareState returns an accessor for the identified software status.
|
|
// Index should be within the range of [0..SoftwareOneshotTypeCount[ .
|
|
func (state *GameState) OneshotSoftwareState(index int) CountedSoftwareState {
|
|
return CountedSoftwareState{
|
|
startOffset: installedOneshotSoftwareStartOffset,
|
|
limit: SoftwareOneshotTypeCount,
|
|
Index: index,
|
|
State: state,
|
|
}
|
|
}
|
|
|
|
// EMailState returns an accessor for the identified EMail status.
|
|
// Index should be within the range of [0..EMailCount[ .
|
|
func (state *GameState) EMailState(index int) MessageState {
|
|
return MessageState{
|
|
startOffset: emailStatusStartOffset,
|
|
limit: EMailCount,
|
|
Index: index,
|
|
State: state,
|
|
}
|
|
}
|
|
|
|
// LogState returns an accessor for the identified log status.
|
|
// Index should be within the range of [0..LogCount[ .
|
|
func (state *GameState) LogState(index int) MessageState {
|
|
return MessageState{
|
|
startOffset: logStatusStartOffset,
|
|
limit: LogCount,
|
|
Index: index,
|
|
State: state,
|
|
}
|
|
}
|
|
|
|
// FragmentState returns an accessor for the identified fragment status.
|
|
// Index should be within the range of [0..FragmentCount[ .
|
|
func (state *GameState) FragmentState(index int) MessageState {
|
|
return MessageState{
|
|
startOffset: fragmentStatusStartOffset,
|
|
limit: FragmentCount,
|
|
Index: index,
|
|
State: state,
|
|
}
|
|
}
|
|
|
|
// ZeroGameStateData returns the state block reset for default engine behaviour.
|
|
func ZeroGameStateData() []byte {
|
|
return make([]byte, GameStateSize)
|
|
}
|
|
|
|
// DefaultGameState returns a GameState that can be used for any mission.
|
|
func DefaultGameState() *GameState {
|
|
data := ZeroGameStateData()
|
|
state := NewGameState(data)
|
|
|
|
state.Set("Health", 212)
|
|
state.Set("Cyberspace integrity", 255)
|
|
state.Set("Health regeneration per minute", 0)
|
|
state.Set("Power", 255)
|
|
state.Set("Cyberspace base time", 30*60*engineTicksPerSecond)
|
|
state.Set("Shield absorb rate", 0)
|
|
state.Set("Fatigue regeneration", 0)
|
|
state.Set("Fatigue regeneration base", 100)
|
|
state.Set("Fatigue regeneration max", 400)
|
|
state.Set("Accuracy", 100)
|
|
|
|
for i := 0; i < InventoryWeaponSlots; i++ {
|
|
state.InventoryWeaponSlot(i).SetFree()
|
|
}
|
|
for i := 0; i < GrenadeTypeCount; i++ {
|
|
state.InventoryGrenade(i).SetTimerSetting(grenadeTimerDefault)
|
|
}
|
|
|
|
for index, info := range engineBooleanVariables {
|
|
if info.InitValue == nil {
|
|
continue
|
|
}
|
|
state.SetBooleanVar(index, *info.InitValue != 0)
|
|
}
|
|
for index, info := range engineIntegerVariables {
|
|
if info.InitValue == nil {
|
|
continue
|
|
}
|
|
state.SetIntegerVar(index, *info.InitValue)
|
|
}
|
|
|
|
return state
|
|
}
|