Skip to content

3.1.9.1528

Latest

Choose a tag to compare

@SphereII SphereII released this 28 Jul 18:31

`Version: 3.1.9.1528
Game Version: v3.1.0 (b13)
[ EntitySyncUtils - ItemValue Stats And Mod Serialization ]
- ItemValue gained a Stats array (ItemValue.Stat[]) holding special
modifiers. EntitySyncUtils serializes an NPC into the pickup item's
metadata when a companion is collected, so anything it does not write
is lost when that NPC is put back down. Stats were not written, so a
collected NPC's gear came back stripped of them.
- ItemValue.Stat is a struct of (PassiveEffects type, bool isBoosted,
short value). Its constructor takes (_type, _base, _added) but stores
only the sum and "was anything added", so the base/added split cannot
be recovered. The three fields are round-tripped directly rather than
through the constructor, which would have meant inventing a split.
- Mods were serialized as a bare item name, so a modded weapon returned
with default mods: the mod's own quality, durability and Stats were
all discarded. A mod now carries all four.
- Mods are also written positionally. Empty slots used to be skipped,
so [scope, empty, silencer] came back as a two element array with the
silencer at index 0. An empty slot is now an empty entry, preserving
both array length and mod indexes. An item whose slots are all empty
still serializes as an empty field, exactly as before.
- Mod ItemValues are rebuilt with the (id, _bCreateDefaultParts: false)
constructor so the restored state is not overwritten by default parts.

	[ Slot string format ]
	- Each slot gained a sixth comma separated field:
	      name,count,quality,useTimes,mods,stats
	- Separator hierarchy, outermost first. Mods nesting their own stats
	  means every level needs its own character:
	      ';'  slots
	      ','  fields within a slot
	      '|'  mods within the mods field
	      '@'  fields within one mod: name@quality@useTimes@stats
	      '~'  stats within any stats field (a slot's own, or a mod's)
	      ':'  fields within one stat: type:isBoosted:value
	- Backward compatible in both directions. Strings written before this
	  change have five fields and a name-only mods list; every field past
	  the first is optional on read, and Stats is left null, which is the
	  state a freshly constructed ItemValue already carries. An older build
	  reading a new string ignores the extra field.
	- An effect name this build no longer knows is dropped rather than
	  parsed loosely: a failed Enum.TryParse would leave the type at
	  PassiveEffects 0, which is a real effect, and would silently apply
	  the wrong modifier to the restored gear.

	[ Bug fixed while extending the format ]
	- UseTimes and Quality were written and parsed at the current culture
	  inside a COMMA delimited field. On any locale that formats decimals
	  with a comma (de-DE, fr-FR, most of Europe), a UseTimes of 1.5 was
	  written as "1,5", which split into two columns and corrupted every
	  field after it in that slot - mods included. All numeric writes and
	  parses in EntitySyncUtils are now CultureInfo.InvariantCulture. This
	  affected the existing item level fields, not only the new mod ones.

[ LockPicking - A Successful Pick On A Cop Car Set Off The Alarm ]
	- Picking a cntPoliceCar01 open left the alarm variant behind. The car
	  became cntPoliceCar01AlarmLocked - jammed, loot list policeCarsBonus,
	  policeCarAlarmPrefab - instead of the cntPoliceCar01PickedLockBonus
	  it had correctly been downgraded to a moment earlier. Nothing was
	  wrong with the block definition; it is what vanilla ships. The alarm
	  was also not DowngradeEvent firing, which only runs from
	  Block.OnBlockDamaged. The alarm block was simply being placed.
	- XUiC_PickLocking.OnClose ran twice per pick. Its own last lines call
	  windowManager.Close(ID), and GUIWindowManager.Close ->
	  XUiWindowGroup.OnClose -> Controller.OnClose re-enters the method
	  that called it.
	- The second pass still saw the lock as solved. SphereLocks.Disable
	  only did SetActive(false); Keyhole.LockComplete() was untouched, and
	  GetComponent still resolves on an inactive object, so IsLockOpened()
	  kept returning true. ResetLock() only ever ran from Enable().
	- By then LockedFeature had been cleared, so the guard that keeps the
	  generic block level downgrade out of the TEFeatureLockPickable path
	  no longer applied, and that downgrade overwrote the block the
	  feature had just placed. onSelfLockpickSuccess fired twice as well.
	- Two fixes. SphereLocks.Disable now calls Keyhole.ResetLock() before
	  deactivating, so a closed window never reports a solved lock; the
	  null check on the component matters, because Init() calls Disable()
	  before the Keyhole is attached. XUiC_PickLocking gained a `closing`
	  flag around OnClose's body, in a try/finally so the non main thread
	  early return still clears it - the re-entrant call now runs
	  base.OnClose() and returns.

	[ Block.LockpickDowngradeBlock is dead in V2 ]
	- That downgrade preferred currentBlock.Block.LockpickDowngradeBlock
	  and fell back to DowngradeBlock. The field still exists on Block,
	  but nothing parses it from XML any more - the only
	  "LockPickDowngradeBlock" string left in Assembly-CSharp belongs to
	  TEFeatureLockPickable - so it is always air, and the fallback could
	  only ever resolve to DowngradeBlock. On a block whose DowngradeBlock
	  is a damage state (cars, safes) that is the wrong block to place on
	  success. The dead branch has been removed.
	- What remains of that swap now only runs on the ILockable path,
	  secure doors and containers, where SetLocked(false) has already done
	  the unlocking. It is A21 era leftover and worth revisiting - a
	  secure wood door's DowngradeBlock is its damaged variant - but it
	  was left alone rather than changing door behaviour as part of a cop
	  car fix.

[ Config - Two Region File Guards Now Ship Off ]
	- RegionFileOptimizeLayoutLock and WarnRenderMapLiveServer now default
	  to false in Config/blocks.xml. Neither patch changed; they are opt
	  in now, and setting either back to true restores the previous
	  behaviour exactly.
	- RegionFileOptimizeLayoutLock serializes RegionFileV2.OptimizeLayout
	  against chunk reads and writes. Vanilla runs that compaction without
	  the region file's instance lock, so a chunk load overlapping it can
	  read garbage ("EXCEPTION: In load chunk" / "Wrong chunk header!")
	  and the chunk is then deleted and regenerated. Off by default now
	  puts it in line with RegionFileLoadChunkLock, the other region file
	  lock, which has always shipped off.
	- WarnRenderMapLiveServer is dedicated server only and log only. It
	  warns when rendermap or the web map's full render runs while a game
	  is loaded, which opens a second region file reader over the live
	  save and can tear chunk reads. Turn it on when diagnosing chunks
	  that keep coming back regenerated on a server that renders maps.

`