-
Notifications
You must be signed in to change notification settings - Fork 1
My contribution to stabilizing release #365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
649bfc7
2218d63
df877d1
b5477f3
a153929
f3977a2
ffbf5d6
b833c9c
da2b7ee
d6a1eff
c17aa93
ecde8d8
a96a179
b10b80b
2cb2a49
27f90a1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,3 +12,4 @@ public class NpcLoader : MonoBehaviour | |
| public bool IsLoaded; | ||
| } | ||
| } | ||
|
|
||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -489,8 +489,13 @@ public void StartTrackVobPositionUpdates(GameObject go) | |
| } | ||
|
|
||
| if (index == -1) | ||
| Logger.LogError($"Couldn't find object in Culling list {rootGo.name}. Culling updates will break.", | ||
| { | ||
| // Dynamically spawned items (loot panel, backpack fill) are not registered in the culling lists. | ||
| // This is expected — just skip tracking for them. | ||
| Logger.LogWarning($"VOB {rootGo.name} not in culling list — skipping position tracking (dynamically spawned).", | ||
| LogCat.Vob); | ||
| return; | ||
| } | ||
|
|
||
| _pausedVobs.Add(rootGo, new Tuple<VobList, int>(vobType, index)); | ||
| } | ||
|
|
@@ -542,6 +547,11 @@ private IEnumerator StopTrackVobPositionUpdatesDelayed(GameObject rootGo) | |
| { | ||
| yield return new WaitForSeconds(1f); | ||
| _pausedVobsToReenableCoroutine.Remove(rootGo); | ||
|
|
||
| // GO may have been destroyed (e.g., loot panel closed) during the 1-second delay. | ||
| if (rootGo == null) | ||
| yield break; | ||
|
|
||
| if (!_pausedVobsToReenable.ContainsKey(rootGo)) | ||
| { | ||
| _pausedVobsToReenable.Add(rootGo, rootGo.GetComponentInChildren<Rigidbody>()); | ||
|
|
@@ -567,9 +577,11 @@ private IEnumerator StopVobTrackingBasedOnVelocity() | |
| continue; | ||
| } | ||
|
|
||
| UpdateSpherePosition(key); | ||
| rigidBody.isKinematic = true; | ||
| // Item may not be in _pausedVobs if it was dynamically spawned and never registered in culling lists. | ||
| if (_pausedVobs.ContainsKey(key)) | ||
| UpdateSpherePosition(key); | ||
|
|
||
| rigidBody.isKinematic = true; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh very good one. Otherwise they always keep their physiscs. Also after disabling->enabling. You saved us a lot of headache for future performance bottlenecks if we have too many physical objects... |
||
| _pausedVobs.Remove(key); | ||
| _pausedVobsToReenable.Remove(key); | ||
| } | ||
|
|
@@ -593,6 +605,10 @@ private void UpdateSpherePosition(GameObject go) | |
| _ => throw new ArgumentOutOfRangeException() | ||
| }; | ||
|
|
||
| // Index may be stale if other VOBs were removed from the list after this item was grabbed. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this will happen as we currently don't add dynamic (new) objects into the list. But it's ok as a safety check and won't harm. |
||
| if (index >= sphereList.Count) | ||
| return; | ||
|
|
||
| sphereList[index] = new BoundingSphere(go.transform.position, sphereList[index].radius); | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -466,7 +466,7 @@ private GameObject CreateItemMesh(ItemInstance item, GameObject go, GameObject p | |
|
|
||
| if (mrm != null) | ||
| { | ||
| return _meshService.CreateVob(item.Visual, mrm, parent: parent, rootGo: go, useColliderCache: true); | ||
| return _meshService.CreateVob(item.Visual, mrm, parent: parent, rootGo: go, useColliderCache: true, useTextureArray: false); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why did you put it to false? Each item's mesh should be cached already so that you can use the texture array. 🤔
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Swords and bows when taken out of the loot socket had 'earth' texture - Weapons exclusive to an NPC's inventory were never pre-cached into the world TextureArray, causing the shader to fall back to array index 0 (a terrain texture). Fixed by passing useTextureArray: false |
||
| } | ||
|
|
||
| // shortbow (itrw_bow_l_01) has no mrm, but has mmb | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,16 @@ | ||
| using Gothic.Core.Adapters.UI.StatusBars; | ||
| using Gothic.Core.Domain.Npc.Actions.AnimationActions; | ||
| using Gothic.Core.Logging; | ||
| using Gothic.Core.Manager; | ||
| using Gothic.Core.Models.Container; | ||
| using Gothic.Core.Models.Vm; | ||
| using Gothic.Core.Services.Config; | ||
| using Gothic.Core.Services.Vm; | ||
| using Gothic.Core.Services.World; | ||
| using Reflex.Attributes; | ||
| using UnityEngine; | ||
| using ZenKit.Daedalus; | ||
| using Logger = Gothic.Core.Logging.Logger; | ||
|
|
||
| namespace Gothic.Core.Services.Npc | ||
| { | ||
|
|
@@ -18,21 +21,28 @@ public class FightService | |
| [Inject] private AnimationService _animationService; | ||
| [Inject] private PhysicsService _physicsService; | ||
| [Inject] private NpcHelperService _npcHelperService; | ||
| [Inject] private readonly ConfigService _configService; | ||
|
|
||
| public void Init() | ||
| { | ||
| if (!_configService.Dev.EnableCombatSystem) | ||
| return; | ||
|
|
||
| GlobalEventDispatcher.FightHit.AddListener(OnHit); | ||
| } | ||
|
|
||
| private void OnHit(NpcContainer attacker, NpcContainer target, Vector3 __) | ||
| { | ||
| Logger.Log($"[FightService.OnHit] *** {attacker.Instance.GetName(NpcNameSlot.Slot0)} HIT {target.Instance.GetName(NpcNameSlot.Slot0)}", LogCat.Npc); | ||
| if (OnHitUpdateHealth(attacker, target)) | ||
| { | ||
| Logger.Log($"[FightService.OnHit] {target.Instance.GetName(NpcNameSlot.Slot0)} is DEAD", LogCat.Npc); | ||
| target.Props.BodyState = VmGothicEnums.BodyState.BsDead; | ||
| OnDyingChangeAnimation(target); | ||
| } | ||
| else | ||
| { | ||
| Logger.Log($"[FightService.OnHit] {target.Instance.GetName(NpcNameSlot.Slot0)} took damage, playing hurt animation", LogCat.Npc); | ||
| OnHitChangeAnimation(target); | ||
| OnHitPlaySound(target); | ||
| } | ||
|
|
@@ -46,17 +56,34 @@ private bool OnHitUpdateHealth(NpcContainer attacker, NpcContainer target) | |
| { | ||
| // FIXME - We need to handle this via power and skill level of attacker, not weapon alone. | ||
| var hitPoints = target.Vob.GetAttribute((int)NpcAttribute.HitPoints); | ||
| var maxHP = target.Vob.GetAttribute((int)NpcAttribute.HitPointsMax); | ||
|
|
||
| var equippedWeapon = _npcHelperService.ExtNpcGetEquippedMeleeWeapon(attacker.Instance); | ||
| Logger.Log($"[FightService.OnHitUpdateHealth] Attacker: {attacker.Instance.GetName(NpcNameSlot.Slot0)}, WeaponName: {(equippedWeapon != null ? equippedWeapon.Name : "None")}, Damage: {(equippedWeapon != null ? equippedWeapon.DamageTotal.ToString() : "N/A")}", LogCat.Npc); | ||
| // FIXME - Instead of 0, use fist value | ||
| // FIXME - Instead of DamageTotal, use calculated NPC/Hero value | ||
| var damage = equippedWeapon?.DamageTotal ?? 0; | ||
| if (damage <= 0) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you find a weapon with <0 damage? Which one was it?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Uriziel :D
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well uriziel has got 0 damage not less than zero but <= is a arithmethic check habit |
||
| damage = 10; // debug: force minimum 10 until proper damage calculation is implemented | ||
|
|
||
| Logger.Log($"[FightService.OnHitUpdateHealth] {target.Instance.GetName(NpcNameSlot.Slot0)}: {hitPoints} - {damage} dmg", LogCat.Npc); | ||
|
|
||
| hitPoints -= damage; | ||
|
|
||
| Logger.Log($"[FightService.OnHitUpdateHealth] {target.Instance.GetName(NpcNameSlot.Slot0)} HP after: {hitPoints}/{maxHP}", LogCat.Npc); | ||
|
|
||
| target.Vob.SetAttribute((int)NpcAttribute.HitPoints, hitPoints); | ||
|
|
||
| target.Go.GetComponentInChildren<StatusBarAdapter>(true)?.SetFillAmount(hitPoints, target.Vob.GetAttribute((int)NpcAttribute.HitPointsMax)); | ||
| var statusBar = target.Go.GetComponentInChildren<StatusBarAdapter>(true); | ||
| if (statusBar != null) | ||
| { | ||
| Logger.Log($"[FightService.OnHitUpdateHealth] Updating HP bar for {target.Instance.GetName(NpcNameSlot.Slot0)}", LogCat.Npc); | ||
| statusBar.SetFillAmount(hitPoints, maxHP); | ||
| } | ||
| else | ||
| { | ||
| Logger.LogWarning($"[FightService.OnHitUpdateHealth] No StatusBar found for {target.Instance.GetName(NpcNameSlot.Slot0)}", LogCat.Npc); | ||
| } | ||
|
|
||
| return hitPoints <= 0; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -111,6 +111,30 @@ public List<ContentItem> GetInventoryItems(NpcInstance npc, VmGothicEnums.InvCat | |
| return _vobService.UnpackItems(npcVob.GetPacked((int)category)); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Returns all items across every category. Each InvCats slot only exists in ZenKit if | ||
| /// SetPacked was previously called for it — accessing a missing slot throws from native code. | ||
| /// This method silently skips slots that were never initialized. | ||
| /// </summary> | ||
| public List<ContentItem> GetAllInventoryItems(NpcInstance npc) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good one. I like this method. Short, pinpointed, and working. :-) |
||
| { | ||
| var items = new List<ContentItem>(); | ||
| foreach (VmGothicEnums.InvCats cat in System.Enum.GetValues(typeof(VmGothicEnums.InvCats))) | ||
| { | ||
| if (cat == VmGothicEnums.InvCats.InvCatMax) | ||
| continue; | ||
| try | ||
| { | ||
| items.AddRange(GetInventoryItems(npc, cat)); | ||
| } | ||
| catch | ||
| { | ||
| // Slot was never initialized for this NPC — expected when a category has no items | ||
| } | ||
| } | ||
| return items; | ||
| } | ||
|
|
||
| public int ExtNpcHasItems(NpcInstance npc, int itemId) | ||
| { | ||
| var npcVob = npc.GetUserData()!.Vob; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -228,9 +228,16 @@ public FreePoint FindNearestFreePoint(Vector3 lookupPosition, string fpNamePart, | |
|
|
||
| public DijkstraWaypoint[] FindFastestPath(string startWaypoint, string endWaypoint) | ||
| { | ||
| // Get the start and end waypoints from the DijkstraWaypoints dictionary | ||
| var startDijkstraWaypoint = _gameStateService.DijkstraWaypoints[startWaypoint]; | ||
| var endDijkstraWaypoint = _gameStateService.DijkstraWaypoints[endWaypoint]; | ||
| if (!_gameStateService.DijkstraWaypoints.ContainsKey(startWaypoint)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you find errors for this waypoint topic? Out of curiousity: Which NPC?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Molerats had SPAWN_MOLELRAT_TOTU_LEFT_PLAT4 set as their navigation target, but this waypoint only exists as a spawn point — it's not registered as a navigation node in the WayNet. FindFastestPath was doing a direct dictionary lookup (dict[key]), which threw a KeyNotFoundException, and the returned null then caused a cascade NullReferenceException in GoToWp.GetWalkDestination. Fixed by guarding with ContainsKey before the lookup and returning null early with a warning log. |
||
| { | ||
| Logger.LogWarning($"FindFastestPath: start waypoint '{startWaypoint}' not found in WayNet.", LogCat.Npc); | ||
| return null; | ||
| } | ||
| if (!_gameStateService.DijkstraWaypoints.TryGetValue(endWaypoint, out var endDijkstraWaypoint)) | ||
| { | ||
| Logger.LogWarning($"FindFastestPath: end waypoint '{endWaypoint}' not found in WayNet.", LogCat.Npc); | ||
| return null; | ||
| } | ||
|
|
||
| // Initialize the previousNodes dictionary to keep track of the path | ||
| var previousNodes = new Dictionary<string, DijkstraWaypoint>(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah, good one. We should add it to the list later. But the comment remarks it already as TODO.