Adding modding folder with a hacked build.

This commit is contained in:
ml
2026-09-26 08:53:08 +02:00
parent cdb960dc71
commit c91080aba7
755 changed files with 50424 additions and 0 deletions
@@ -0,0 +1,73 @@
package object
// Bitmap3D describes how a bitmap object should be represented in 3D world.
type Bitmap3D uint16
const (
bitmap3DBitmapNumberLowMask uint16 = 0x03FF
bitmap3DBitmapNumberHighMask uint16 = 0x8000
bitmap3DBitmapNumberMask = bitmap3DBitmapNumberLowMask | bitmap3DBitmapNumberHighMask
bitmap3DAnimationMask uint16 = 0x0400
bitmap3DRepeatMask uint16 = 0x0800
bitmap3DFrameNumberMask uint16 = 0x7000
// Bitmap3DBitmapNumberLimit is the maximum amount of bitmaps.
Bitmap3DBitmapNumberLimit uint16 = 0x07FF
// Bitmap3DFrameNumberLimit is the maximum amount of frames.
Bitmap3DFrameNumberLimit uint16 = 0x0007
)
// WithBitmapNumber returns a Bitmap3D with the given value.
func (bmp Bitmap3D) WithBitmapNumber(value uint16) Bitmap3D {
bitmapValue := value & bitmap3DBitmapNumberLowMask
if value >= Bitmap3DBitmapNumberLimit {
bitmapValue = bitmap3DBitmapNumberMask
} else if value > bitmap3DBitmapNumberLowMask {
bitmapValue |= bitmap3DBitmapNumberHighMask
}
return Bitmap3D((uint16(bmp) & ^bitmap3DBitmapNumberMask) | bitmapValue)
}
// BitmapNumber returns the number of bitmaps.
func (bmp Bitmap3D) BitmapNumber() uint16 {
return ((uint16(bmp) & bitmap3DBitmapNumberHighMask) >> 5) | (uint16(bmp) & bitmap3DBitmapNumberLowMask)
}
// WithFrameNumber returns a Bitmap3D with the given value.
func (bmp Bitmap3D) WithFrameNumber(value uint16) Bitmap3D {
frameValue := (value % (Bitmap3DFrameNumberLimit + 1)) << 12
return Bitmap3D((uint16(bmp) & ^bitmap3DFrameNumberMask) | frameValue)
}
// FrameNumber returns the frame number.
func (bmp Bitmap3D) FrameNumber() uint16 {
return (uint16(bmp) & bitmap3DFrameNumberMask) >> 12
}
// WithAnimation returns a Bitmap3D with the given value.
func (bmp Bitmap3D) WithAnimation(value bool) Bitmap3D {
animationValue := uint16(0)
if value {
animationValue = bitmap3DAnimationMask
}
return Bitmap3D((uint16(bmp) & ^bitmap3DAnimationMask) | animationValue)
}
// Animation returns whether this object is animated.
func (bmp Bitmap3D) Animation() bool {
return (uint16(bmp) & bitmap3DAnimationMask) != 0
}
// WithRepeat returns a Bitmap3D with the given value.
func (bmp Bitmap3D) WithRepeat(value bool) Bitmap3D {
repeatValue := uint16(0)
if value {
repeatValue = bitmap3DRepeatMask
}
return Bitmap3D((uint16(bmp) & ^bitmap3DRepeatMask) | repeatValue)
}
// Repeat returns whether this object repeats its animation.
func (bmp Bitmap3D) Repeat() bool {
return (uint16(bmp) & bitmap3DRepeatMask) != 0
}
@@ -0,0 +1,40 @@
package object_test
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/inkyblackness/hacked/ss1/content/object"
)
func TestBitmap3DValueRange(t *testing.T) {
for _, animation := range []bool{false, true} {
for _, repeat := range []bool{false, true} {
for frameNumber := uint16(0); frameNumber <= object.Bitmap3DFrameNumberLimit; frameNumber++ {
for _, bitmapNumber := range []uint16{
0,
object.Bitmap3DBitmapNumberLimit / 4,
object.Bitmap3DBitmapNumberLimit / 2,
object.Bitmap3DBitmapNumberLimit / 2,
object.Bitmap3DBitmapNumberLimit/2 + 4,
object.Bitmap3DBitmapNumberLimit - 1,
object.Bitmap3DBitmapNumberLimit,
} {
key := fmt.Sprintf("%v:%v:%v:%v", animation, repeat, frameNumber, bitmapNumber)
value := object.Bitmap3D(0).
WithAnimation(animation).
WithRepeat(repeat).
WithFrameNumber(frameNumber).
WithBitmapNumber(bitmapNumber)
assert.Equal(t, animation, value.Animation(), fmt.Sprintf("Animation wrong for %v", key))
assert.Equal(t, repeat, value.Repeat(), fmt.Sprintf("Repeat wrong for %v", key))
assert.Equal(t, frameNumber, value.FrameNumber(), fmt.Sprintf("Frame number wrong for %v", key))
assert.Equal(t, bitmapNumber, value.BitmapNumber(), fmt.Sprintf("Bitmap number wrong for %v", key))
}
}
}
}
}
@@ -0,0 +1,160 @@
package object
import "fmt"
// CommonFlag describe flags applicable to all object classes.
type CommonFlag int
// String returns the textual representation of the value.
func (flag CommonFlag) String() string {
if int(flag) >= len(commonFlagNames) {
return fmt.Sprintf("Unknown 0x%04X", int(flag))
}
return commonFlagNames[flag]
}
func (flag CommonFlag) mask() CommonFlagField {
return CommonFlagField(1<<uint(flag)) & commonFlagFieldMask
}
// Common flags.
const (
CommonFlagUseful CommonFlag = 0
CommonFlagSolid CommonFlag = 1
CommonFlagUsableInventoryObject CommonFlag = 4
CommonFlagBlockRendering CommonFlag = 5
CommonFlagSolidIfClosed CommonFlag = 8
CommonFlagFlatSolid CommonFlag = 9
CommonFlagDoubledBitmapSize CommonFlag = 10
CommonFlagDestroyOnContact CommonFlag = 11
CommonFlagClassSpecific1 CommonFlag = 12
CommonFlagClassSpecific2 CommonFlag = 13
CommonFlagClassSpecific3 CommonFlag = 14
CommonFlagUseless CommonFlag = 15
)
var commonFlagNames = []string{
"Useful",
"Solid",
"Not a Flag: 0x0004",
"Not a Flag: 0x0008",
"UsableInventoryObject",
"BlockRendering",
"Not a Flag: 0x0040",
"Not a Flag: 0x0080",
"SolidIfClosed",
"FlatSolid",
"DoubleBitmapSize",
"DestroyOnContact",
"ClassSpecific1",
"ClassSpecific2",
"ClassSpecific3",
"Useless",
}
// CommonFlags returns all known constants.
func CommonFlags() []CommonFlag {
return []CommonFlag{
CommonFlagUseful, CommonFlagSolid, CommonFlagUsableInventoryObject, CommonFlagBlockRendering,
CommonFlagSolidIfClosed, CommonFlagFlatSolid, CommonFlagDoubledBitmapSize, CommonFlagDestroyOnContact,
CommonFlagClassSpecific1, CommonFlagClassSpecific2, CommonFlagClassSpecific3, CommonFlagUseless,
}
}
const (
commonFlagFieldMask = 0xFF33
commonFlagLightTypeShift = 6
commonFlagLightTypeMask = 0x00C0
commonFlagUseModeShift = 2
commonFlagUseModeMask = 0x000C
)
// CommonFlagField describes simple features applicable for all object classes.
type CommonFlagField uint16
// Has returns whether the field has given flag set.
func (field CommonFlagField) Has(flag CommonFlag) bool {
return (field & flag.mask()) != 0
}
// With returns a new field instance with given flag set.
func (field CommonFlagField) With(flag CommonFlag) CommonFlagField {
return field | flag.mask()
}
// Without returns a new field instance without the given flag set.
func (field CommonFlagField) Without(flag CommonFlag) CommonFlagField {
return field & ^flag.mask()
}
// LightType returns the stored type enumeration value.
func (field CommonFlagField) LightType() LightType {
return LightType((field & commonFlagLightTypeMask) >> commonFlagLightTypeShift)
}
// WithLightType returns a new flag field value with given light type set.
func (field CommonFlagField) WithLightType(value LightType) CommonFlagField {
return CommonFlagField((uint16(field) & ^uint16(commonFlagLightTypeMask)) | ((uint16(value) << commonFlagLightTypeShift) & commonFlagLightTypeMask))
}
// UseMode returns the stored enumeration value.
func (field CommonFlagField) UseMode() UseMode {
return UseMode((field & commonFlagUseModeMask) >> commonFlagUseModeShift)
}
// WithUseMode returns a new flag field value with given use mode set.
func (field CommonFlagField) WithUseMode(value UseMode) CommonFlagField {
return CommonFlagField((uint16(field) & ^uint16(commonFlagUseModeMask)) | ((uint16(value) << commonFlagUseModeShift) & commonFlagUseModeMask))
}
// LightType describes how an object is affected by lighting.
type LightType byte
// String returns the textual representation of the value.
func (mode LightType) String() string {
if int(mode) >= len(lightTypeNames) {
return fmt.Sprintf("Unknown 0x%02X", int(mode))
}
return lightTypeNames[mode]
}
// LightType constants.
const (
LightTypeSimple LightType = 0x00
LightTypeComplex LightType = 0x01
LightTypeIgnore LightType = 0x02
LightTypeDeferred LightType = 0x03
)
var lightTypeNames = []string{"Simple", "Complex", "Ignore", "Deferred"}
// LightTypes returns all known constants.
func LightTypes() []LightType {
return []LightType{LightTypeSimple, LightTypeComplex, LightTypeIgnore, LightTypeDeferred}
}
// UseMode describes how an object can be interacted with.
type UseMode byte
// String returns the textual representation of the value.
func (mode UseMode) String() string {
if int(mode) >= len(useModeNames) {
return fmt.Sprintf("Unknown 0x%02X", int(mode))
}
return useModeNames[mode]
}
// UseMode constants.
const (
UseModePickup UseMode = 0x00
UseModeUse UseMode = 0x01
UseModeNull UseMode = 0x03
)
var useModeNames = []string{"Pickup", "Use", "Unknown 0x02", "Null"}
// UseModes returns all known constants.
func UseModes() []UseMode {
return []UseMode{UseModePickup, UseModeUse, UseModeNull}
}
@@ -0,0 +1,98 @@
package object_test
import (
"fmt"
"testing"
"github.com/inkyblackness/hacked/ss1/content/object"
"github.com/stretchr/testify/assert"
)
func TestLightTypes(t *testing.T) {
assert.Equal(t, 4, len(object.LightTypes()))
}
func TestLightTypeString(t *testing.T) {
tt := []struct {
lightType object.LightType
expected string
}{
{lightType: object.LightTypeSimple, expected: "Simple"},
{lightType: object.LightTypeComplex, expected: "Complex"},
{lightType: object.LightTypeIgnore, expected: "Ignore"},
{lightType: object.LightTypeDeferred, expected: "Deferred"},
{lightType: object.LightType(255), expected: "Unknown 0xFF"},
}
for _, tc := range tt {
result := tc.lightType.String()
assert.Equal(t, tc.expected, result, fmt.Sprintf("Failed for 0x%02X", int(tc.lightType)))
}
}
func TestUseModes(t *testing.T) {
assert.Equal(t, 3, len(object.UseModes()))
}
func TestUseModeString(t *testing.T) {
tt := []struct {
useMode object.UseMode
expected string
}{
{useMode: object.UseModePickup, expected: "Pickup"},
{useMode: object.UseModeUse, expected: "Use"},
{useMode: object.UseModeNull, expected: "Null"},
{useMode: object.UseMode(255), expected: "Unknown 0xFF"},
}
for _, tc := range tt {
result := tc.useMode.String()
assert.Equal(t, tc.expected, result, fmt.Sprintf("Failed for 0x%02X", int(tc.useMode)))
}
}
func TestCommonFlagField(t *testing.T) {
for _, useMode := range object.UseModes() {
for _, lightType := range object.LightTypes() {
for _, flag := range object.CommonFlags() {
key := fmt.Sprintf("%v:%v:%v", useMode, lightType, flag)
field := object.CommonFlagField(0).WithUseMode(useMode).WithLightType(lightType).With(flag)
assert.Equal(t, useMode, field.UseMode(), fmt.Sprintf("UseMode wrong for %v", key))
assert.Equal(t, lightType, field.LightType(), fmt.Sprintf("LightType wrong for %v", key))
assert.True(t, field.Has(flag), fmt.Sprintf("flag not set for %v", key))
assert.False(t, field.Without(flag).Has(flag), fmt.Sprintf("flag still set for %v", key))
}
}
}
}
func TestCommonFlagString(t *testing.T) {
tt := []struct {
flag object.CommonFlag
expected string
}{
{flag: object.CommonFlagUseful, expected: "Useful"},
{flag: object.CommonFlagSolid, expected: "Solid"},
{flag: object.CommonFlagUsableInventoryObject, expected: "UsableInventoryObject"},
{flag: object.CommonFlagBlockRendering, expected: "BlockRendering"},
{flag: object.CommonFlagSolidIfClosed, expected: "SolidIfClosed"},
{flag: object.CommonFlagFlatSolid, expected: "FlatSolid"},
{flag: object.CommonFlagDoubledBitmapSize, expected: "DoubleBitmapSize"},
{flag: object.CommonFlagDestroyOnContact, expected: "DestroyOnContact"},
{flag: object.CommonFlagClassSpecific1, expected: "ClassSpecific1"},
{flag: object.CommonFlagClassSpecific2, expected: "ClassSpecific2"},
{flag: object.CommonFlagClassSpecific3, expected: "ClassSpecific3"},
{flag: object.CommonFlagUseless, expected: "Useless"},
{flag: object.CommonFlag(255), expected: "Unknown 0x00FF"},
}
for _, tc := range tt {
result := tc.flag.String()
assert.Equal(t, tc.expected, result, fmt.Sprintf("Failed for 0x%02X", int(tc.flag)))
}
}
@@ -0,0 +1,39 @@
package object
const (
// CommonPropertiesSize specifies, in bytes, the length a common properties structure has.
CommonPropertiesSize = 27
// HardnessLimit is the maximum hardness value.
HardnessLimit = 253
// PhysicsXRLimit is the maximum size value.
PhysicsXRLimit = 253
// DefenseNoCriticals is the defense value that prohibits criticals.
DefenseNoCriticals = 0xFF
// ToughnessNoDamage is the toughness value that prohibits regular damage.
ToughnessNoDamage = 3
)
// CommonProperties are generic ones available for all objects.
type CommonProperties struct {
Mass int32
Hitpoints int16
Armor byte
RenderType RenderType
PhysicsModel PhysicsModel
Hardness byte
_ byte // pep: not used
PhysicsXR byte
_ byte // physics_y: not used
PhysicsZ byte
Vulnerabilities DamageTypeMask
SpecialVulnerabilities SpecialDamageType
_ uint16 // remainder of "resistances": not used
Defense byte
Toughness byte
Flags CommonFlagField
MfdOrMeshID uint16
Bitmap3D Bitmap3D
DestroyEffect DestroyEffect
}
@@ -0,0 +1,8 @@
package object
const (
// ClassCount describes how many object classes there are.
ClassCount = 15
propertiesFileVersion uint32 = 0x0000002D
)
@@ -0,0 +1,103 @@
package object
import "fmt"
// DamageType classifies damage.
type DamageType byte
// String returns the textual representation of the value.
func (damageType DamageType) String() string {
if int(damageType) >= len(damageTypeNames) {
return fmt.Sprintf("Unknown 0x%02X", int(damageType))
}
return damageTypeNames[damageType]
}
func (damageType DamageType) mask() DamageTypeMask {
return 1 << damageType
}
// DamageType constants.
const (
DamageTypeExplosion DamageType = 0
DamageTypeEnergy DamageType = 1
DamageTypeMagnetic DamageType = 2
DamageTypeRadiation DamageType = 3
DamageTypeGas DamageType = 4
DamageTypeTranquilizer DamageType = 5
DamageTypeNeedle DamageType = 6
DamageTypeBio DamageType = 7
)
var damageTypeNames = []string{
"Explosion", "Energy", "Magnetic", "Radiation",
"Gas", "Tranquilizer", "Needle", "Bio",
}
// DamageTypes returns all known constants.
func DamageTypes() []DamageType {
return []DamageType{
DamageTypeExplosion, DamageTypeEnergy, DamageTypeMagnetic, DamageTypeRadiation,
DamageTypeGas, DamageTypeTranquilizer, DamageTypeNeedle, DamageTypeBio,
}
}
// DamageTypeMask combines a set of damage types.
type DamageTypeMask byte
// Has returns whether the mask contains the specified value.
func (mask DamageTypeMask) Has(dmg DamageType) bool {
return (mask & dmg.mask()) != 0
}
// With returns a new mask that specifies the combination of this and the given damage type.
func (mask DamageTypeMask) With(dmg DamageType) DamageTypeMask {
return mask | dmg.mask()
}
// Without returns a new mask that specifies the remainder of this, not including the given type.
func (mask DamageTypeMask) Without(dmg DamageType) DamageTypeMask {
return mask & ^dmg.mask()
}
// SpecialDamageType is a combination of "primary" (double) and "super" (quadruple) damage potential.
// The identifier are "freeform" enumerations without public constants.
type SpecialDamageType byte
const (
specialDamageTypePrimaryShift = 0
specialDamageTypePrimaryMask = 0x0F
specialDamageTypeSuperShift = 4
specialDamageTypeSuperMask = 0xF0
// SpecialDamageTypeLimit identifies the maximum value of special damage type.
SpecialDamageTypeLimit = 0x0F
)
// PrimaryValue returns the double damage type.
func (dmg SpecialDamageType) PrimaryValue() int {
return int((dmg & specialDamageTypePrimaryMask) >> specialDamageTypePrimaryShift)
}
// WithPrimaryValue returns a new type instance with the given primary value set.
func (dmg SpecialDamageType) WithPrimaryValue(value int) SpecialDamageType {
result := dmg
if (value >= 0) && (value <= SpecialDamageTypeLimit) {
result = SpecialDamageType((byte(result) & ^byte(specialDamageTypePrimaryMask)) | byte(value<<specialDamageTypePrimaryShift))
}
return result
}
// SuperValue returns the quadruple damage type.
func (dmg SpecialDamageType) SuperValue() int {
return int((dmg & specialDamageTypeSuperMask) >> specialDamageTypeSuperShift)
}
// WithSuperValue returns a new type instance with the given super value set.
func (dmg SpecialDamageType) WithSuperValue(value int) SpecialDamageType {
result := dmg
if (value >= 0) && (value <= SpecialDamageTypeLimit) {
result = SpecialDamageType((byte(result) & ^byte(specialDamageTypeSuperMask)) | byte(value<<specialDamageTypeSuperShift))
}
return result
}
@@ -0,0 +1,71 @@
package object_test
import (
"fmt"
"testing"
"github.com/inkyblackness/hacked/ss1/content/object"
"github.com/stretchr/testify/assert"
)
func TestDamageTypes(t *testing.T) {
assert.Equal(t, 8, len(object.DamageTypes()))
}
func TestDamageTypeString(t *testing.T) {
tt := []struct {
damageType object.DamageType
expected string
}{
{damageType: object.DamageTypeExplosion, expected: "Explosion"},
{damageType: object.DamageTypeEnergy, expected: "Energy"},
{damageType: object.DamageTypeMagnetic, expected: "Magnetic"},
{damageType: object.DamageTypeRadiation, expected: "Radiation"},
{damageType: object.DamageTypeGas, expected: "Gas"},
{damageType: object.DamageTypeTranquilizer, expected: "Tranquilizer"},
{damageType: object.DamageTypeNeedle, expected: "Needle"},
{damageType: object.DamageTypeBio, expected: "Bio"},
{damageType: object.DamageType(255), expected: "Unknown 0xFF"},
}
for _, tc := range tt {
result := tc.damageType.String()
assert.Equal(t, tc.expected, result, fmt.Sprintf("Failed for 0x%02X", int(tc.damageType)))
}
}
func TestDamageTypeMask(t *testing.T) {
allTypes := object.DamageTypes()
noDmg := object.DamageTypeMask(0)
allDmg := ^noDmg
for _, dmg := range allTypes {
assert.False(t, noDmg.Has(dmg), fmt.Sprintf("empty mask shouldn't contain damage %v", dmg))
assert.True(t, allDmg.Has(dmg), fmt.Sprintf("full mask should contain damage %v", dmg))
assert.True(t, noDmg.With(dmg).Has(dmg), fmt.Sprintf("With() should return mask including damage %v", dmg))
assert.False(t, allDmg.Without(dmg).Has(dmg), fmt.Sprintf("Without() should return mask excluding damage %v", dmg))
}
}
func TestSpecialDamageType(t *testing.T) {
for super := 0; super <= object.SpecialDamageTypeLimit; super++ {
for primary := 0; primary <= object.SpecialDamageTypeLimit; primary++ {
key := fmt.Sprintf("%v:%v", super, primary)
result1 := object.SpecialDamageType(0).WithSuperValue(super).WithPrimaryValue(primary)
result2 := object.SpecialDamageType(0).WithPrimaryValue(primary).WithSuperValue(super)
assert.Equal(t, result1, result2, fmt.Sprintf("value should be equal regardless of modifier ordering in %v", key))
assert.Equal(t, super, result1.SuperValue(), fmt.Sprintf("super value not found in %v", key))
assert.Equal(t, primary, result1.PrimaryValue(), fmt.Sprintf("primary value not found in %v", key))
}
}
}
func TestSpecialDamageTypeLimits(t *testing.T) {
base := object.SpecialDamageType(0).WithSuperValue(object.SpecialDamageTypeLimit / 2).WithPrimaryValue(object.SpecialDamageTypeLimit/2 - 1)
assert.Equal(t, base, base.WithSuperValue(-1), "negative value not allowed for super")
assert.Equal(t, base, base.WithSuperValue(object.SpecialDamageTypeLimit+1), "beyond limit value not allowed for super")
assert.Equal(t, base, base.WithPrimaryValue(-1), "negative value not allowed for primary")
assert.Equal(t, base, base.WithPrimaryValue(object.SpecialDamageTypeLimit+1), "beyond limit value not allowed for primary")
}
@@ -0,0 +1,44 @@
package object
// Descriptors describes a set of object classes.
type Descriptors []ClassDescriptor
// ClassDescriptor describes a single object class.
type ClassDescriptor struct {
// GenericDataSize specifies the length, in bytes, of one generic type entry.
GenericDataSize int
// Subclasses contains descriptions of the subclasses of this class.
// The index into the array is the subclass ID.
Subclasses []SubclassDescriptor
}
// TotalDataSize returns the total length the class requires.
func (desc ClassDescriptor) TotalDataSize() int {
total := 0
for _, subclass := range desc.Subclasses {
total += (desc.GenericDataSize * subclass.TypeCount) + subclass.TotalDataSize()
}
return total
}
// TotalTypeCount returns the total number of types in this class.
func (desc ClassDescriptor) TotalTypeCount() int {
total := 0
for _, subclass := range desc.Subclasses {
total += subclass.TypeCount
}
return total
}
// SubclassDescriptor describes one subclass.
type SubclassDescriptor struct {
// TypeCount specifies how many types exist in this subclass.
TypeCount int
// SpecificDataSize specifies the length, in bytes, of one specific type entry.
SpecificDataSize int
}
// TotalDataSize returns the total length, in bytes, the subclass requires in the properties file.
func (desc SubclassDescriptor) TotalDataSize() int {
return (desc.SpecificDataSize + CommonPropertiesSize) * desc.TypeCount
}
@@ -0,0 +1,31 @@
package object_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/inkyblackness/hacked/ss1/content/object"
)
func TestClassDescriptorTotalDataLengthReturnsCompleteLength(t *testing.T) {
var mainDesc object.ClassDescriptor
mainDesc.GenericDataSize = 7
mainDesc.Subclasses = append(mainDesc.Subclasses,
object.SubclassDescriptor{TypeCount: 2, SpecificDataSize: 3},
object.SubclassDescriptor{TypeCount: 1, SpecificDataSize: 20})
assert.Equal(t, (7*(2+1))+(2*3)+(1*20)+object.CommonPropertiesSize*3, mainDesc.TotalDataSize())
}
func TestClassDescriptorTotalTypeCountReturnsTotalAmount(t *testing.T) {
var mainDesc object.ClassDescriptor
mainDesc.GenericDataSize = 7
mainDesc.Subclasses = append(mainDesc.Subclasses,
object.SubclassDescriptor{TypeCount: 2, SpecificDataSize: 3},
object.SubclassDescriptor{TypeCount: 1, SpecificDataSize: 20})
assert.Equal(t, 3, mainDesc.TotalTypeCount())
}
@@ -0,0 +1,51 @@
package object
// DestroyEffect is a packed type describing effects during object destruction.
type DestroyEffect byte
const (
destroyEffectValueMask byte = 0x1F
destroyEffectSoundMask byte = 0x60
destroyEffectExplosionMask byte = 0x80
// DestroyEffectValueLimit is the maximum the value can take.
DestroyEffectValueLimit = destroyEffectValueMask
)
// WithValue returns a new destroy effect with given value.
func (effect DestroyEffect) WithValue(value byte) DestroyEffect {
return DestroyEffect((byte(effect) & ^destroyEffectValueMask) | value)
}
// Value returns the effect value.
func (effect DestroyEffect) Value() byte {
return byte(effect) & destroyEffectValueMask
}
// WithSound returns a new destroy effect that has sound playback marked.
func (effect DestroyEffect) WithSound(value bool) DestroyEffect {
soundValue := byte(0)
if value {
soundValue = 0x20
}
return DestroyEffect((byte(effect) & ^destroyEffectSoundMask) | soundValue)
}
// PlaySound returns true if a sound shall be played.
func (effect DestroyEffect) PlaySound() bool {
return (byte(effect) & destroyEffectSoundMask) != 0
}
// WithExplosion returns a new destroy effect that has explosion display marked.
func (effect DestroyEffect) WithExplosion(value bool) DestroyEffect {
explosionValue := byte(0)
if value {
explosionValue = 0x80
}
return DestroyEffect((byte(effect) & ^destroyEffectExplosionMask) | explosionValue)
}
// ShowExplosion returns true if an explosion shall be shown.
func (effect DestroyEffect) ShowExplosion() bool {
return (byte(effect) & destroyEffectExplosionMask) != 0
}
@@ -0,0 +1,23 @@
package object_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/inkyblackness/hacked/ss1/content/object"
)
func TestDestroyEffectValueRange(t *testing.T) {
for _, showExplosion := range []bool{false, true} {
for _, playSound := range []bool{false, true} {
for value := byte(0); value <= object.DestroyEffectValueLimit; value++ {
effect := object.DestroyEffect(0).WithExplosion(showExplosion).WithSound(playSound).WithValue(value)
assert.Equal(t, showExplosion, effect.ShowExplosion(), "Explosion bit wrong")
assert.Equal(t, playSound, effect.PlaySound(), "Sound value wrong")
assert.Equal(t, value, effect.Value(), "Value wrong")
}
}
}
}
@@ -0,0 +1,34 @@
package object
import "fmt"
// PhysicsModel defines how an object can interact with the world.
type PhysicsModel byte
// String returns the textual representation of the value.
func (model PhysicsModel) String() string {
if int(model) >= len(physicsModelNames) {
return fmt.Sprintf("Unknown 0x%02X", int(model))
}
return physicsModelNames[model]
}
// PhysicsModel constants.
const (
PhysicsModelInsubstantial PhysicsModel = 0
PhysicsModelRegular PhysicsModel = 1
PhysicsModelStrange PhysicsModel = 2
)
var physicsModelNames = []string{
"Insubstantial",
"Regular",
"Strange",
}
// PhysicsModels returns all known constants.
func PhysicsModels() []PhysicsModel {
return []PhysicsModel{
PhysicsModelInsubstantial, PhysicsModelRegular, PhysicsModelStrange,
}
}
@@ -0,0 +1,32 @@
package object_test
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/inkyblackness/hacked/ss1/content/object"
)
func TestPhysicsModels(t *testing.T) {
assert.Equal(t, 3, len(object.PhysicsModels()))
}
func TestPhysicsModelString(t *testing.T) {
tt := []struct {
model object.PhysicsModel
expected string
}{
{model: object.PhysicsModelInsubstantial, expected: "Insubstantial"},
{model: object.PhysicsModelRegular, expected: "Regular"},
{model: object.PhysicsModelStrange, expected: "Strange"},
{model: object.PhysicsModel(255), expected: "Unknown 0xFF"},
}
for _, tc := range tt {
result := tc.model.String()
assert.Equal(t, tc.expected, result, fmt.Sprintf("Failed for 0x%02X", int(tc.model)))
}
}
@@ -0,0 +1,17 @@
package object
// Pivot returns the assumed height offset for placing objects.
func Pivot(prop CommonProperties) float32 {
const DefaultHeight = 1.0 / float32(0xbd00)
const PhysicsScale = 96.0
switch {
case (prop.RenderType == RenderTypeTextPoly) || (prop.RenderType == RenderTypeSpecial):
return 0.0
case prop.PhysicsZ != 0:
return float32(prop.PhysicsZ) / PhysicsScale
case prop.PhysicsXR != 0:
return float32(prop.PhysicsXR) / PhysicsScale
}
return DefaultHeight
}
@@ -0,0 +1,160 @@
package object
import (
"github.com/inkyblackness/hacked/ss1"
"github.com/inkyblackness/hacked/ss1/serial"
)
const (
errInvalidClass ss1.StringError = "invalid class"
errInvalidSubclass ss1.StringError = "invalid subclass"
errInvalidType ss1.StringError = "invalid type"
)
// PropertiesTable is a collection of class-specific properties.
type PropertiesTable []ClassProperties
// ClassProperties is a collection of subclass-specific properties.
type ClassProperties []SubclassProperties
// SubclassProperties is a collection of type-specific properties.
type SubclassProperties []Properties
// Properties contains the object-specific properties.
type Properties struct {
Common CommonProperties
Generic []byte
Specific []byte
}
// Clone returns a fresh instance with a copy of the data.
func (prop Properties) Clone() Properties {
cloneBytes := func(in []byte) []byte {
out := make([]byte, len(in))
copy(out, in)
return out
}
return Properties{
Common: prop.Common,
Generic: cloneBytes(prop.Generic),
Specific: cloneBytes(prop.Specific),
}
}
// NewPropertiesTable returns a new instance based on given descriptors.
func NewPropertiesTable(desc Descriptors) PropertiesTable {
classCount := len(desc)
table := make([]ClassProperties, classCount)
for class := 0; class < classCount; class++ {
classDesc := desc[class]
subclassCount := len(classDesc.Subclasses)
subclassProperties := make([]SubclassProperties, subclassCount)
table[class] = subclassProperties
for subclass := 0; subclass < subclassCount; subclass++ {
subclassDesc := classDesc.Subclasses[subclass]
typeProperties := make([]Properties, subclassDesc.TypeCount)
subclassProperties[subclass] = typeProperties
for objType := 0; objType < subclassDesc.TypeCount; objType++ {
prop := &typeProperties[objType]
prop.Generic = make([]byte, classDesc.GenericDataSize)
prop.Specific = make([]byte, subclassDesc.SpecificDataSize)
}
}
}
return table
}
// ForObject returns the object-specific properties by given triple.
func (table PropertiesTable) ForObject(triple Triple) (*Properties, error) {
if int(triple.Class) >= len(table) {
return nil, errInvalidClass
}
classEntry := table[triple.Class]
if int(triple.Subclass) >= len(classEntry) {
return nil, errInvalidSubclass
}
subclassEntry := classEntry[triple.Subclass]
if int(triple.Type) >= len(subclassEntry) {
return nil, errInvalidType
}
return &subclassEntry[triple.Type], nil
}
// Code serializes the table with given coder.
func (table PropertiesTable) Code(coder serial.Coder) {
version := propertiesFileVersion
coder.Code(&version)
for _, subclasses := range table {
for _, types := range subclasses {
for _, prop := range types {
coder.Code(prop.Generic)
}
}
for _, types := range subclasses {
for _, prop := range types {
coder.Code(prop.Specific)
}
}
}
for _, subclasses := range table {
for _, types := range subclasses {
for objType := 0; objType < len(types); objType++ {
coder.Code(&types[objType].Common)
}
}
}
}
// TriplesInClass returns all triples that are available in the given class.
func (table PropertiesTable) TriplesInClass(class Class) []Triple {
var triples []Triple
if int(class) < len(table) {
subclasses := table[class]
for subclass, types := range subclasses {
for objType := range types {
triples = append(triples, TripleFrom(int(class), subclass, objType))
}
}
}
return triples
}
// TripleIndex returns the linear index of the given index.
func (table PropertiesTable) TripleIndex(triple Triple) int {
if int(triple.Class) >= len(table) {
return -1
}
counter := 0
for class := Class(0); class < triple.Class; class++ {
subclasses := table[class]
for _, types := range subclasses {
counter += len(types)
}
}
subclasses := table[triple.Class]
if int(triple.Subclass) >= len(subclasses) {
return -1
}
for subclass := Subclass(0); subclass < triple.Subclass; subclass++ {
counter += len(subclasses[subclass])
}
types := subclasses[triple.Subclass]
if int(triple.Type) >= len(types) {
return -1
}
return counter + int(triple.Type)
}
// Iterate walks through all types sequentially and gives the properties to the given consumer.
// The iteration stops when the consumer returns false or the table has been exhausted.
func (table PropertiesTable) Iterate(consumer func(Triple, *Properties) bool) {
for class, subclasses := range table {
for subclass, types := range subclasses {
for objType := 0; objType < len(types); objType++ {
if !consumer(TripleFrom(class, subclass, objType), &types[objType]) {
return
}
}
}
}
}
@@ -0,0 +1,58 @@
package object_test
import (
"bytes"
"testing"
"github.com/inkyblackness/hacked/ss1/content/object"
"github.com/inkyblackness/hacked/ss1/serial"
"github.com/stretchr/testify/assert"
)
func TestPropertiesForObjectReturnErrorForWrongIDs(t *testing.T) {
tt := []struct {
name string
triple object.Triple
}{
{"wrong class", object.TripleFrom(20, 0, 2)},
{"wrong subclass", object.TripleFrom(1, 40, 0)},
{"wrong type", object.TripleFrom(1, 0, 20)},
}
table := object.StandardPropertiesTable()
for _, tc := range tt {
_, err := table.ForObject(tc.triple)
assert.Error(t, err, "error expected for "+tc.name)
}
}
func TestPropertiesForObjectData(t *testing.T) {
tt := []struct {
triple object.Triple
genSize int
specSize int
}{
{object.TripleFrom(0, 0, 0), 2, 1},
{object.TripleFrom(14, 4, 1), 75, 1},
{object.TripleFrom(10, 3, 4), 1, 1},
}
table := object.StandardPropertiesTable()
for _, tc := range tt {
prop, err := table.ForObject(tc.triple)
assert.Nil(t, err, "no error expected for "+tc.triple.String())
assert.Equal(t, tc.genSize, len(prop.Generic), "wrong generic size for "+tc.triple.String())
assert.Equal(t, tc.specSize, len(prop.Specific), "wrong specific size for "+tc.triple.String())
}
}
func TestPropertiesEncoding(t *testing.T) {
buf := bytes.NewBuffer(nil)
encoder := serial.NewEncoder(buf)
table := object.StandardPropertiesTable()
table.Code(encoder)
result := buf.Bytes()
assert.Equal(t, 17951, len(result)) // as taken from original CD
}
@@ -0,0 +1,48 @@
package object
import "fmt"
// RenderType defines how an object is to be rendered.
type RenderType byte
// String returns the textual representation of the value.
func (renderType RenderType) String() string {
if int(renderType) >= len(renderTypeNames) {
return fmt.Sprintf("Unknown 0x%02X", int(renderType))
}
return renderTypeNames[renderType]
}
// RenderType constants.
const (
RenderTypeUnknown RenderType = 0
RenderTypeTextPoly RenderType = 1
RenderTypeBitmap RenderType = 2
RenderTypeTPoly RenderType = 3
RenderTypeCritter RenderType = 4
RenderTypeAnimPoly RenderType = 5
RenderTypeVox RenderType = 6
RenderTypeNoObject RenderType = 7
RenderTypeTexBitmap RenderType = 8
RenderTypeFlatPoly RenderType = 9
RenderTypeMultiView RenderType = 10
RenderTypeSpecial RenderType = 11
RenderTypeTLPoly RenderType = 12
)
var renderTypeNames = []string{
"Unknown 0x00",
"TextPoly", "Bitmap", "TPoly", "Critter",
"AnimPoly", "Vox", "NoObject", "TexBitmap",
"FlatPoly", "MultiView", "Special", "TLPoly",
}
// RenderTypes returns all known constants.
func RenderTypes() []RenderType {
return []RenderType{
RenderTypeUnknown,
RenderTypeTextPoly, RenderTypeBitmap, RenderTypeTPoly, RenderTypeCritter,
RenderTypeAnimPoly, RenderTypeVox, RenderTypeNoObject, RenderTypeTexBitmap,
RenderTypeFlatPoly, RenderTypeMultiView, RenderTypeSpecial, RenderTypeTLPoly,
}
}
@@ -0,0 +1,42 @@
package object_test
import (
"fmt"
"testing"
"github.com/inkyblackness/hacked/ss1/content/object"
"github.com/stretchr/testify/assert"
)
func TestRenderTypes(t *testing.T) {
assert.Equal(t, 13, len(object.RenderTypes()))
}
func TestRenderTypeString(t *testing.T) {
tt := []struct {
renderType object.RenderType
expected string
}{
{renderType: object.RenderTypeUnknown, expected: "Unknown 0x00"},
{renderType: object.RenderTypeTextPoly, expected: "TextPoly"},
{renderType: object.RenderTypeBitmap, expected: "Bitmap"},
{renderType: object.RenderTypeTPoly, expected: "TPoly"},
{renderType: object.RenderTypeCritter, expected: "Critter"},
{renderType: object.RenderTypeAnimPoly, expected: "AnimPoly"},
{renderType: object.RenderTypeVox, expected: "Vox"},
{renderType: object.RenderTypeNoObject, expected: "NoObject"},
{renderType: object.RenderTypeTexBitmap, expected: "TexBitmap"},
{renderType: object.RenderTypeFlatPoly, expected: "FlatPoly"},
{renderType: object.RenderTypeMultiView, expected: "MultiView"},
{renderType: object.RenderTypeSpecial, expected: "Special"},
{renderType: object.RenderTypeTLPoly, expected: "TLPoly"},
{renderType: object.RenderType(255), expected: "Unknown 0xFF"},
}
for _, tc := range tt {
result := tc.renderType.String()
assert.Equal(t, tc.expected, result, fmt.Sprintf("Failed for 0x%02X", int(tc.renderType)))
}
}
@@ -0,0 +1,177 @@
package object
// StandardPropertiesTable returns a properties table based on the standard
// configuration of the existing objprop.dat file.
func StandardPropertiesTable() PropertiesTable {
return NewPropertiesTable(StandardDescriptors())
}
// StandardDescriptors returns an array of class descriptors that represent the standard
// configuration of the existing objprop.dat file.
func StandardDescriptors() Descriptors {
var result Descriptors
{ // Guns
subclasses := []SubclassDescriptor{
{TypeCount: 5, SpecificDataSize: 1},
{TypeCount: 2, SpecificDataSize: 1},
{TypeCount: 2, SpecificDataSize: 16},
{TypeCount: 2, SpecificDataSize: 13},
{TypeCount: 3, SpecificDataSize: 13},
{TypeCount: 2, SpecificDataSize: 18},
}
result = append(result, ClassDescriptor{GenericDataSize: 2, Subclasses: subclasses})
}
{ // Ammo
subclasses := []SubclassDescriptor{
{TypeCount: 2, SpecificDataSize: 1},
{TypeCount: 2, SpecificDataSize: 1},
{TypeCount: 3, SpecificDataSize: 1},
{TypeCount: 2, SpecificDataSize: 1},
{TypeCount: 2, SpecificDataSize: 1},
{TypeCount: 2, SpecificDataSize: 1},
{TypeCount: 2, SpecificDataSize: 1},
}
result = append(result, ClassDescriptor{GenericDataSize: 14, Subclasses: subclasses})
}
{ // Physics
subclasses := []SubclassDescriptor{
{TypeCount: 6, SpecificDataSize: 20},
{TypeCount: 16, SpecificDataSize: 6},
{TypeCount: 2, SpecificDataSize: 1},
}
result = append(result, ClassDescriptor{GenericDataSize: 1, Subclasses: subclasses})
}
{ // Grenades
subclasses := []SubclassDescriptor{
{TypeCount: 5, SpecificDataSize: 1},
{TypeCount: 3, SpecificDataSize: 3},
}
result = append(result, ClassDescriptor{GenericDataSize: 15, Subclasses: subclasses})
}
{ // Drugs
subclasses := []SubclassDescriptor{
{TypeCount: 7, SpecificDataSize: 7},
}
result = append(result, ClassDescriptor{GenericDataSize: 17, Subclasses: subclasses})
}
{ // Hardware
subclasses := []SubclassDescriptor{
{TypeCount: 5, SpecificDataSize: 1},
{TypeCount: 10, SpecificDataSize: 2},
}
result = append(result, ClassDescriptor{GenericDataSize: 2, Subclasses: subclasses})
}
{ // Software
subclasses := []SubclassDescriptor{
{TypeCount: 7, SpecificDataSize: 1},
{TypeCount: 3, SpecificDataSize: 1},
{TypeCount: 4, SpecificDataSize: 1},
{TypeCount: 5, SpecificDataSize: 1},
{TypeCount: 3, SpecificDataSize: 1},
}
result = append(result, ClassDescriptor{GenericDataSize: 2, Subclasses: subclasses})
}
{ // BigStuff
subclasses := []SubclassDescriptor{
{TypeCount: 9, SpecificDataSize: 1},
{TypeCount: 10, SpecificDataSize: 1},
{TypeCount: 11, SpecificDataSize: 1},
{TypeCount: 4, SpecificDataSize: 1},
{TypeCount: 9, SpecificDataSize: 1},
{TypeCount: 8, SpecificDataSize: 1},
{TypeCount: 16, SpecificDataSize: 1},
{TypeCount: 10, SpecificDataSize: 1},
}
result = append(result, ClassDescriptor{GenericDataSize: 4, Subclasses: subclasses})
}
{ // SmallStuff
subclasses := []SubclassDescriptor{
{TypeCount: 8, SpecificDataSize: 1},
{TypeCount: 10, SpecificDataSize: 1},
{TypeCount: 15, SpecificDataSize: 1},
{TypeCount: 6, SpecificDataSize: 1},
{TypeCount: 12, SpecificDataSize: 1},
{TypeCount: 12, SpecificDataSize: 6},
{TypeCount: 9, SpecificDataSize: 1},
{TypeCount: 8, SpecificDataSize: 2},
}
result = append(result, ClassDescriptor{GenericDataSize: 2, Subclasses: subclasses})
}
{ // Fixtures
subclasses := []SubclassDescriptor{
{TypeCount: 9, SpecificDataSize: 1},
{TypeCount: 7, SpecificDataSize: 1},
{TypeCount: 3, SpecificDataSize: 1},
{TypeCount: 11, SpecificDataSize: 1},
{TypeCount: 2, SpecificDataSize: 0}, // skipped vending machines
{TypeCount: 3, SpecificDataSize: 1},
}
result = append(result, ClassDescriptor{GenericDataSize: 1, Subclasses: subclasses})
}
{ // Doors
subclasses := []SubclassDescriptor{
{TypeCount: 10, SpecificDataSize: 1},
{TypeCount: 9, SpecificDataSize: 1},
{TypeCount: 7, SpecificDataSize: 1},
{TypeCount: 5, SpecificDataSize: 1},
{TypeCount: 10, SpecificDataSize: 1},
}
result = append(result, ClassDescriptor{GenericDataSize: 1, Subclasses: subclasses})
}
{ // Animating
subclasses := []SubclassDescriptor{
{TypeCount: 9, SpecificDataSize: 1},
{TypeCount: 11, SpecificDataSize: 1},
{TypeCount: 14, SpecificDataSize: 1},
}
result = append(result, ClassDescriptor{GenericDataSize: 2, Subclasses: subclasses})
}
{ // Traps
subclasses := []SubclassDescriptor{
{TypeCount: 13, SpecificDataSize: 1},
{TypeCount: 1, SpecificDataSize: 1},
{TypeCount: 5, SpecificDataSize: 1},
}
result = append(result, ClassDescriptor{GenericDataSize: 1, Subclasses: subclasses})
}
{ // Containers
subclasses := []SubclassDescriptor{
{TypeCount: 3, SpecificDataSize: 1},
{TypeCount: 3, SpecificDataSize: 1},
{TypeCount: 4, SpecificDataSize: 1},
{TypeCount: 8, SpecificDataSize: 1},
{TypeCount: 13, SpecificDataSize: 1},
{TypeCount: 7, SpecificDataSize: 1},
{TypeCount: 8, SpecificDataSize: 1},
}
result = append(result, ClassDescriptor{GenericDataSize: 3, Subclasses: subclasses})
}
{ // Critters
subclasses := []SubclassDescriptor{
{TypeCount: 9, SpecificDataSize: 1},
{TypeCount: 12, SpecificDataSize: 2},
{TypeCount: 7, SpecificDataSize: 2},
{TypeCount: 7, SpecificDataSize: 6},
{TypeCount: 2, SpecificDataSize: 1},
}
result = append(result, ClassDescriptor{GenericDataSize: 75, Subclasses: subclasses})
}
return result
}
@@ -0,0 +1,29 @@
package object_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/inkyblackness/hacked/ss1/content/object"
)
func TestStandardDescriptorsReturnsProperLength(t *testing.T) {
descriptor := object.StandardDescriptors()
totalLength := 4 // version prefix
for _, classDesc := range descriptor {
totalLength += classDesc.TotalDataSize()
}
assert.Equal(t, 17951, totalLength) // as taken from original CD
}
func TestStandardDescriptorsReturnsProperAmount(t *testing.T) {
descriptor := object.StandardDescriptors()
total := 0
for _, classDesc := range descriptor {
total += classDesc.TotalTypeCount()
}
assert.Equal(t, 476, total)
}
+104
View File
@@ -0,0 +1,104 @@
package object
import "fmt"
// Class describes a general category of objects.
type Class byte
// String returns the textual representation.
func (c Class) String() string {
if int(c) >= len(classNames) {
return fmt.Sprintf("Unknown%02X", int(c))
}
return classNames[c]
}
// Object classes constants.
const (
ClassGun Class = 0
ClassAmmo Class = 1
ClassPhysics Class = 2
ClassGrenade Class = 3
ClassDrug Class = 4
ClassHardware Class = 5
ClassSoftware Class = 6
ClassBigStuff Class = 7
ClassSmallStuff Class = 8
ClassFixture Class = 9
ClassDoor Class = 10
ClassAnimating Class = 11
ClassTrap Class = 12
ClassContainer Class = 13
ClassCritter Class = 14
)
var classNames = []string{
"Gun",
"Ammo",
"Physics",
"Grenade",
"Drug",
"Hardware",
"Software",
"BigStuff",
"SmallStuff",
"Fixture",
"Door",
"Animating",
"Trap",
"Container",
"Critter",
}
// Classes returns a list of all classes.
func Classes() []Class {
var classes [ClassCount]Class
for class := Class(0); class < ClassCount; class++ {
classes[class] = class
}
return classes[:]
}
// Subclass divides an object class.
type Subclass byte
// Type describes one specific object.
type Type byte
// Triple identifies one specific object by its full coordinate.
type Triple struct {
Class Class
Subclass Subclass
Type Type
}
// TripleFrom returns a Triple instance with given values as coordinates.
func TripleFrom(class, subclass, objType int) Triple {
return Triple{
Class: Class(class),
Subclass: Subclass(subclass),
Type: Type(objType),
}
}
// TripleFromInt returns a Triple instance based on a single integer value.
func TripleFromInt(value int) Triple {
return Triple{
Class: Class((value >> 16) & 0xFF),
Subclass: Subclass((value >> 8) & 0xFF),
Type: Type((value >> 0) & 0xFF),
}
}
// String returns the textual representation of the triple as "cl/s/ty" string.
func (triple Triple) String() string {
return fmt.Sprintf("%2d/%d/%2d", triple.Class, triple.Subclass, triple.Type)
}
// Int returns the triple as a single integer value.
func (triple Triple) Int() int {
value := int(triple.Class) << 16
value |= int(triple.Subclass) << 8
value |= int(triple.Type) << 0
return value
}
@@ -0,0 +1,50 @@
package object_test
import (
"fmt"
"testing"
"github.com/inkyblackness/hacked/ss1/content/object"
"github.com/stretchr/testify/assert"
)
func TestClasses(t *testing.T) {
classes := object.Classes()
assert.Equal(t, 15, len(classes))
}
func TestClassString(t *testing.T) {
assert.Equal(t, "Gun", object.Class(0).String())
assert.Equal(t, "UnknownFF", object.Class(0xFF).String())
}
func TestTripleString(t *testing.T) {
tt := []struct {
triple object.Triple
expected string
}{
{object.TripleFrom(1, 2, 3), " 1/2/ 3"},
{object.TripleFrom(20, 0, 10), "20/0/10"},
}
for _, tc := range tt {
result := tc.triple.String()
assert.Equal(t, tc.expected, result)
}
}
func TestTripleIntegerRepresentation(t *testing.T) {
tt := []struct {
triple object.Triple
expected int
}{
{object.TripleFrom(1, 2, 3), 0x00010203},
{object.TripleFrom(20, 0, 10), 0x0014000A},
}
for _, tc := range tt {
result := tc.triple.Int()
assert.Equal(t, tc.expected, result, fmt.Sprintf("integer value not matching for %v", tc.triple))
reversed := object.TripleFromInt(tc.expected)
assert.Equal(t, tc.triple, reversed, fmt.Sprintf("reverse value not matching for %v", tc.triple))
}
}
+2
View File
@@ -0,0 +1,2 @@
// Package object contains the game-global properties of objects.
package object
@@ -0,0 +1,20 @@
package objprop
import (
"github.com/inkyblackness/hacked/ss1/content/interpreters"
"github.com/inkyblackness/hacked/ss1/content/object"
)
var ammoClipGenerics = interpreters.New().
Refining("BasicWeapon", 0, 8, basicWeapon, interpreters.Always).
With("CartrigeSize", 8, 1).
With("BulletMass", 9, 1).
With("BulletSpeed", 10, 2).As(interpreters.RangedValue(-10000, +10000)).
With("Range", 12, 1).
With("RecoilForce", 13, 1)
func initAmmo() {
objClass := object.Class(1)
genericDescriptions[objClass] = ammoClipGenerics
}
@@ -0,0 +1,26 @@
package objprop
import (
"fmt"
"github.com/inkyblackness/hacked/ss1/content/interpreters"
"github.com/inkyblackness/hacked/ss1/content/object"
)
var animationGenerics = interpreters.New().
With("FrameTime", 0, 1).As(interpreters.FormattedRangedValue(0, 255,
func(value int) string {
return fmt.Sprintf("%3.0f millisec", (float64(value)*900)/255.0)
})).
With("Flags", 1, 1).As(interpreters.Bitfield(map[uint32]string{0x01: "Emit Light"}))
var explosionAnimation = interpreters.New().
With("FrameExplode", 0, 1)
func initAnimating() {
objClass := object.Class(11)
genericDescriptions[objClass] = animationGenerics
setSpecific(objClass, 2, explosionAnimation)
}
@@ -0,0 +1,72 @@
package objprop
import (
"github.com/inkyblackness/hacked/ss1/content/interpreters"
"github.com/inkyblackness/hacked/ss1/content/object"
)
var critterAttackInfo = interpreters.New().
With("DamageType", 0x0000, 1).As(damageType).
With("SpecialDamageType", 0x0001, 1).As(specialDamageType).
With("DamageModifier", 0x0004, 2).As(interpreters.RangedValue(0, 500)).
With("OffenceValue", 0x0006, 1).
With("Penetration", 0x0007, 1).
With("AttackMass", 0x0008, 1).
With("AttackVelocity", 0x0009, 2).
With("Accuracy", 0x000B, 1).
With("AttackRange", 0x000C, 1).
With("ReloadTime", 0x000D, 2).As(interpreters.RangedValue(0, 1000)).
With("Projectile", 0x0011, 4).As(interpreters.SpecialValue("ObjectTriple"))
var critterGenerics = interpreters.New().
Refining("PrimaryAttack", 0x0001, 21, critterAttackInfo, interpreters.Always).
Refining("SecondaryAttack", 0x0016, 21, critterAttackInfo, interpreters.Always).
With("Perception", 0x002B, 1).
With("Defence", 0x002C, 1).
With("ProjectileSourceHeightOffset", 0x002D, 1).As(interpreters.RangedValue(-128, 127)).
With("Flags", 0x002E, 1).As(interpreters.Bitfield(map[uint32]string{
0x01: "Flying"})).
With("AnimationSpeed", 0x003B, 1).
With("AttackSoundIndex", 0x003C, 1).
With("NearSoundIndex", 0x003D, 1).
With("HurtSoundIndex", 0x003E, 1).
With("DeathSoundIndex", 0x003F, 1).
With("NoticeSoundIndex", 0x0040, 1).
With("Corpse", 0x0041, 4).As(interpreters.SpecialValue("ObjectTriple")).
With("ViewCount", 0x0045, 1).
With("SecondaryAttackProbability", 0x0046, 1).
With("DisruptProbability", 0x0047, 1).
With("TreasureType", 0x0048, 1).As(interpreters.EnumValue(map[uint32]string{
0x00: "No treasure",
0x01: "Humanoid",
0x02: "Drone",
0x03: "Assassin",
0x04: "Warrior cyborg",
0x05: "Flier bot",
0x06: "Security 1 bot",
0x07: "Exec bot",
0x08: "Cyborg enforcer",
0x09: "Security 2 bot",
0x0A: "Elite cyborg",
0x0B: "Standard corpse",
0x0C: "Loot-oriented corpse",
0x0D: "Electro-stuff treasure",
0x0E: "Serv-bot"})).
With("HitEffect", 0x0049, 1).As(interpreters.EnumValue(map[uint32]string{0: "meat", 1: "plant", 2: "robot", 3: "cyborg", 4: "generic"})).
With("AttackKeyFrame", 0x004A, 1).As(interpreters.RangedValue(0, 10))
var cyberCritters = interpreters.New().
With("Color0", 0, 1).
With("Color1", 1, 1).
With("Color2", 2, 1).
With("AltColor0", 3, 1).
With("AltColor1", 4, 1).
With("AltColor2", 5, 1)
func initCritters() {
objClass := object.Class(14)
genericDescriptions[objClass] = critterGenerics
setSpecific(objClass, 3, cyberCritters)
}
@@ -0,0 +1,13 @@
package objprop
import (
"github.com/inkyblackness/hacked/ss1/content/interpreters"
)
var cyberColorScheme = interpreters.New().
With("Color0", 0, 1).
With("Color1", 1, 1).
With("Color2", 2, 1).
With("Color3", 3, 1).
With("Color4", 4, 1).
With("Color5", 5, 1)
@@ -0,0 +1,15 @@
package objprop
import (
"github.com/inkyblackness/hacked/ss1/content/interpreters"
"github.com/inkyblackness/hacked/ss1/content/object"
)
// GenericProperties returns an interpreter specific for the given object class.
func GenericProperties(objClass object.Class, data []byte) *interpreters.Instance {
desc := genericDescriptions[objClass]
if desc == nil {
desc = interpreters.New()
}
return desc.For(data)
}
@@ -0,0 +1,31 @@
package objprop
import (
"github.com/inkyblackness/hacked/ss1/content/interpreters"
"github.com/inkyblackness/hacked/ss1/content/object"
)
var grenadeGenerics = interpreters.New().
Refining("BasicWeapon", 0, 8, basicWeapon, interpreters.Always).
With("Touchiness", 8, 1).
With("BlastRadius", 9, 1).
With("BlastCoreRange", 10, 1).
With("BlastDamage", 11, 1).
With("AttackMass", 12, 1).
With("Flags", 13, 1).As(interpreters.Bitfield(map[uint32]string{
0x01: "Explode on contact",
0x04: "Explode by timer",
0x08: "Explode by vicinity"}))
var timedExplosives = interpreters.New().
With("MinimumTime", 0, 1).
With("MaximumTime", 1, 1).
With("RandomFactor", 2, 1)
func initGrenades() {
objClass := object.Class(3)
genericDescriptions[objClass] = grenadeGenerics
setSpecific(objClass, 1, timedExplosives)
}
@@ -0,0 +1,64 @@
package objprop
import (
"github.com/inkyblackness/hacked/ss1/content/interpreters"
"github.com/inkyblackness/hacked/ss1/content/object"
)
var basicWeapon = interpreters.New().
With("Damage", 0, 2).As(interpreters.RangedValue(0, 0x7FFF)).
With("OffenceValue", 2, 1).
With("DamageType", 3, 1).As(damageType).
With("SpecialDamageType", 4, 1).As(specialDamageType).
With("ArmorPenetration", 7, 1)
var weaponGenerics = interpreters.New().
With("FireRate", 0, 1).
With("AmmoType", 1, 1).As(interpreters.Bitfield(map[uint32]string{
0x01: "AmmoType0",
0x02: "AmmoType1",
0x04: "AmmoType2",
0x08: "AmmoType3",
0xF0: "AmmoSubclass"}))
var projectileWeapons = interpreters.New().
Refining("BasicWeapon", 0, 8, basicWeapon, interpreters.Always).
With("ProjectileTravelSpeed", 8, 1).
With("Projectile", 9, 4).As(interpreters.SpecialValue("ObjectTriple")).
With("AttackMass", 13, 1).
With("AttackSpeed", 14, 2).As(interpreters.RangedValue(-10000, +10000))
var meleeWeapons = interpreters.New().
Refining("BasicWeapon", 0, 8, basicWeapon, interpreters.Always).
With("EnergyUsage", 8, 1).
With("AttackMass", 9, 1).
With("Range", 10, 1).
With("AttackSpeed", 11, 2).As(interpreters.RangedValue(-10000, +10000))
var energyBeamWeapons = interpreters.New().
Refining("BasicWeapon", 0, 8, basicWeapon, interpreters.Always).
With("MaxCharge", 8, 1).
With("AttackMass", 9, 1).
With("Range", 10, 1).
With("AttackSpeed", 11, 2).As(interpreters.RangedValue(-10000, +10000))
var energyProjectileWeapons = interpreters.New().
Refining("BasicWeapon", 0, 8, basicWeapon, interpreters.Always).
With("MaxCharge", 8, 1).
With("AttackMass", 9, 1).
With("AttackSpeed", 10, 2).As(interpreters.RangedValue(-10000, +10000)).
With("ProjectileTravelSpeed", 12, 1).
With("Projectile", 13, 4).As(interpreters.SpecialValue("ObjectTriple")).
With("Flags", 17, 1).As(interpreters.Bitfield(map[uint32]string{
0x02: "Stun"}))
func initGuns() {
objClass := object.Class(0)
genericDescriptions[objClass] = weaponGenerics
setSpecific(objClass, 2, projectileWeapons)
setSpecific(objClass, 3, meleeWeapons)
setSpecific(objClass, 4, energyBeamWeapons)
setSpecific(objClass, 5, energyProjectileWeapons)
}
@@ -0,0 +1,28 @@
package objprop
import (
"github.com/inkyblackness/hacked/ss1/content/interpreters"
"github.com/inkyblackness/hacked/ss1/content/object"
)
var projectileGenerics = interpreters.New().
With("Flags", 0, 1).As(interpreters.Bitfield(map[uint32]string{
0x01: "EmitLight",
0x02: "BounceOffWalls",
0x04: "BounceOffObjects",
0x08: "BounceOffProjectiles"}))
var cyberProjectiles = interpreters.New().
Refining("ColorScheme", 0, 6, cyberColorScheme, interpreters.Always)
func initPhysics() {
objClass := object.Class(2)
genericDescriptions[objClass] = projectileGenerics
setSpecificByType(objClass, 1, 9, cyberProjectiles)
setSpecificByType(objClass, 1, 10, cyberProjectiles)
setSpecificByType(objClass, 1, 11, cyberProjectiles)
setSpecificByType(objClass, 1, 12, cyberProjectiles)
setSpecificByType(objClass, 1, 13, cyberProjectiles)
}
@@ -0,0 +1,15 @@
package objprop
import (
"github.com/inkyblackness/hacked/ss1/content/interpreters"
"github.com/inkyblackness/hacked/ss1/content/object"
)
var cyberItems = interpreters.New().
Refining("ColorScheme", 0, 6, cyberColorScheme, interpreters.Always)
func initSmallStuff() {
objClass := object.Class(8)
setSpecific(objClass, 5, cyberItems)
}
@@ -0,0 +1,18 @@
package objprop
import (
"github.com/inkyblackness/hacked/ss1/content/interpreters"
"github.com/inkyblackness/hacked/ss1/content/object"
)
// SpecificProperties returns an interpreter specific for the given object class and subclass.
func SpecificProperties(triple object.Triple, data []byte) *interpreters.Instance {
desc := specificDescriptions[triple]
if desc == nil {
desc = specificDescriptions[object.TripleFrom(int(triple.Class), int(triple.Subclass), anyObjectType)]
}
if desc == nil {
desc = interpreters.New()
}
return desc.For(data)
}
@@ -0,0 +1,48 @@
package objprop
import (
"github.com/inkyblackness/hacked/ss1/content/interpreters"
"github.com/inkyblackness/hacked/ss1/content/object"
)
var genericDescriptions map[object.Class]*interpreters.Description
var specificDescriptions map[object.Triple]*interpreters.Description
const anyObjectType = 0xFF
var damageType = interpreters.Bitfield(map[uint32]string{
0x01: "Explosion",
0x02: "Energy",
0x04: "Magnetic",
0x08: "Radiation",
0x10: "Gas",
0x20: "Tranquilizer",
0x40: "Needle",
0x80: "Bio",
})
var specialDamageType = interpreters.Bitfield(map[uint32]string{
0x0F: "Primary (double)",
0xF0: "Super (quad)",
})
func init() {
genericDescriptions = make(map[object.Class]*interpreters.Description)
specificDescriptions = make(map[object.Triple]*interpreters.Description)
initGuns()
initAmmo()
initPhysics()
initGrenades()
initSmallStuff()
initAnimating()
initCritters()
}
func setSpecific(objClass object.Class, objSubclass int, desc *interpreters.Description) {
specificDescriptions[object.TripleFrom(int(objClass), objSubclass, anyObjectType)] = desc
}
func setSpecificByType(objClass object.Class, objSubclass int, objType int, desc *interpreters.Description) {
specificDescriptions[object.TripleFrom(int(objClass), objSubclass, objType)] = desc
}