Adding modding folder with a hacked build.
This commit is contained in:
@@ -0,0 +1,825 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user