All findings below come from the installed build (Steam App 4001890, How to Fish_Data/Managed/Assembly-CSharp.dll, 870,400 bytes, dated 2026-09-04), decompiled with ILSpy 11.1 (ilspycmd). Nothing here is guessed; every class, field and method name was read from the decompiled source.
| Item | Finding | Evidence |
|---|---|---|
| Engine | Unity 6000.4.4f1 | globalgamemanagers header; Player.log "Initialize engine version: 6000.4.4f1 (360f97ecca93)" |
| Scripting backend | Mono (not IL2CPP) | Managed/ folder with Assembly-CSharp.dll, MonoBleedingEdge/, no GameAssembly.dll / global-metadata.dat |
| Render pipeline | URP | Unity.RenderPipelines.Universal.Runtime.dll, GameInfo._urpAsset, GameInfo._globalVolume |
| Networking | FishNet (FishNet.Runtime.dll), transports: Steam (Heathen/Steamworks.NET) + FishyUnityTransport via Multipass |
ConnectionManager.SetTransport, FishNet.SDK.Id |
| Input | New Input System and legacy UnityEngine.Input both active |
PlayerInput actions (PlayerMove, PlayerLeftClick, …) plus Input.GetKeyDown(KeyCode.M) in MoneyManager.Update |
| Content | Addressables (StreamingAssets/aa), Localization tables, Resources.LoadAll<Item>("Items") |
GameInfo.Awake |
| Save system | JSON text files in %USERPROFILE%\AppData\LocalLow\Dazed Games\How to Fish\Saves\<SteamID>\ (local.txt = per-player journal/skins, <Server name>.txt = world) |
SaveSystem._saveRootFolder |
| Mod loaders present | None (no BepInEx/, MelonLoader/, winhttp.dll, version.dll) |
directory listing |
| Anti-cheat | None found (no EAC/BattlEye files, no integrity checks in code) | directory listing, code search |
Modding approach chosen: BepInEx 5.4.23.5 (x64, Mono) + HarmonyX, with game assemblies referenced through BepInEx.AssemblyPublicizer so private fields are accessed directly instead of by string reflection. No original game file is modified; BepInEx adds winhttp.dll, doorstop_config.ini, .doorstop_version and BepInEx/.
FishNet with a listen-server host. Server (NetworkBehaviour, singleton Server.Instance) is the central RPC hub: ~60 [ServerRpc(RequireOwnership = false)] methods.
| Data | Authority | Where |
|---|---|---|
| Player position/rotation | Client (sent each tick) | Player.SendPosRot → Server.UpdatePlayerPosRot |
| Projectile spawn + hit detection | Client (shooter simulates, then tells server) | Weapon.Shoot → ProjectileManager.AddProjectile(isLocal:true) → Hit → Creature.LocalHit → Server.HitCreature |
| Creature HP | Host (Creature._hp SyncVar) but damage value is supplied by the client |
Server.RpcLogic___HitCreature → Creature.ServerChangeHp(damage) |
| Killscore multiplier | Client computes, host stores (only if still 1.0) | Server.SetItemMultiplier → Item.SetKillscoreMultiplier |
| Player health/hunger | Host | PlayerVitals.TakeDamage, LowerFullness gate on IsServerInitialized |
| Damage to players | Sent by whoever simulates the attacker | AttackingFish.DamageOnCollision → Server.HitPlayer |
| Money | Host (MoneyManager._money SyncVar) |
MoneyManager.AddMoney/SellItem/RemoveMoney |
| Fish bite / selection / weight / drip | Host | CreatureManager.FindFishForBait (server tick) |
| Island | Host (OnlineIslandManager._curIsland SyncVar) |
SpawnIsland |
Consequence: aim/weapon/movement features work in any role; god mode, hunger, money, fish manipulation and spawning require being the host (single-player is always host: ConnectionManager.CreateOfflineLobby starts server + client).
GameInfo (MonoBehaviour, static facade)
├─ LevelLayer / ItemLayer / ProjectileHitLayer / NpcLayer / BoatLayer … (LayerMasks)
├─ HeadShotDamageMulti (default 1.5), CooknessWorthCurve
├─ _nameToSpawnable / _idToSpawnable / _allItems ← Resources.LoadAll<Item>("Items") (item database)
├─ _allCreatures ← Resources.LoadAll<Creature>("Items/Creatures")
├─ AllBaits (BaitInfo SOs), AllAttachments
└─ Seed (random seed; lobby id when on Steam)
Player (NetworkBehaviour) static Player.LocalPlayer
├─ Movement PlayerMovement custom Rigidbody controller
├─ Camera PlayerCamera _rot (x=pitch,y=yaw) → CamTransform
├─ AimAssist PlayerAimAssist GetRotationDelta() added to _rot every frame (controller only)
├─ Holding PlayerHolding HeldItem, _grabRange, pickup/interact scan
├─ Inventory PlayerInventory slots, CurBait, owned baits
├─ Vitals PlayerVitals _syncedHealth/_syncedFullness/_syncedPoison/_syncedFire
├─ ToolMovement PlayerToolMovement weapon sway / bob / recoil rig
├─ KillScore PlayerKillScore killscore UI queue
├─ Punching PlayerPunching fists (melee without an item)
└─ LocalTeleport(pos, rot, instant) → PlayerMovement.Teleport + flag sent to server
Item (NetworkBehaviour) ItemManager.Items : Dictionary<Transform, Item>
├─ ID (byte), GetName() (localized), _worth, _weight, _syncedRandomWeight, _cookness,
│ _bettingMultiplier, _killScoreMultiplier, _rodAttachedTo, IsQuestItem
├─ TotalWorth = (int)((int)(_worth × weight × CooknessWorthCurve(cookness) × betting) × killScoreMultiplier)
├─ LocalHit(...) virtual
├─ Tool : Item (_anim : Animation)
│ ├─ Weapon : Tool (guns, see §4)
│ ├─ Melee : Tool (bat/knife/etc., damage = _sharpnessUpgrades[idx].Damage)
│ └─ FishingRod : Tool → FishingRodCast / FishingRodCrab
├─ Explosive : Item (dynamite; ExplosionManager.ServerExplode)
├─ Creature : Item (_hp SyncVar, _isDrip SyncVar, HeadPos, BossType, IsEndangered)
│ ├─ Fish : Creature → AttackingFish → Piranha; Pufferfish; RunningFish
│ ├─ Crab, Bird (seagull), Albatross
│ └─ (BowheadWhale is a boss creature)
├─ DeadPlayer, Radio, Map, Disc
Weapon : Tool fields (serialized per prefab): _weaponInfo : WeaponInfo, _attachments : Attachments, _fullAuto, _canAds, _noShootingDuringShootAnim, _noQueueingShots, _timeBetweenShots, _projSpeed, _spread, _projectileCountPerShot, _recoilKnockback. Runtime: Ammo (private set), IsAds, _aimPercent, _isReloading, _hasCoolDown.
WeaponInfo (serializable class, sent over network): ProjectileType (byte), ProjectileDamage, ProjectileForce, ProjectileGravity, ShootVFX, BoatForceOverride.
Attachments (per-weapon NetworkBehaviour): sights (AdsFov, AimSwayMulti, UseSniperUi), barrel (FirePoint, ScreenRecoilAmount, ModelRecoilPos/Rot, WeaponRecoilMulti, spring values), bullets (Damage = _bulletUpgrades[idx].Damage), AmmoPerMag (default/extended mag).
Fire pipeline (all weapons share it; differences are data):
PlayerHolding.PrimaryInput (Input action "PlayerLeftClick", ignored if Player.BlockInputs)
→ Weapon.PrimaryInput → Weapon.Shoot() [also Update() for full-auto / queued]
├─ HasCooldown() (_hasCoolDown, or Fire anim playing if _noShootingDuringShootAnim)
├─ Recoil: AddModelRecoil (ToolMovement recoil rig), Camera.Recoil(ScreenRecoilAmount),
│ ToolMovement.Recoil, Movement.Knockback(-cam.forward × _recoilKnockback)
├─ Ammo--; cooldown Invoke("CoolDown", _timeBetweenShots)
├─ direction = Attachments.FirePoint.forward (the *weapon model* muzzle; sway & model recoil move it)
│ or camera forward when sniper-scoped (_aimPercent > 0.9)
├─ GunClippingRay(): camera→muzzle raycast; if blocked, hit applied directly and projectile spawned at y=-10000
├─ spread: per pellet Quaternion.Euler(random unit vector × _spread) × forward × _projSpeed
└─ ProjectileManager.AddProjectile / AddProjectiles(owner, weaponInfo, isLocal:true, pos, velocity[])
├─ Server.AddProjectile(s) (RPC → other clients simulate a copy)
├─ Damage = OneShot ? 99999 : Weapon.Damage (Attachments.Damage)
├─ ProjectileType.IsHitScan → HitScan(): SphereCast(∞) else FixedUpdate: SphereCast per step
│ gravity: Velocity += down × GravityForce × dt; removed beyond _maxProjectileRange from owner
└─ Hit(): Item → item.LocalHit(...); Player → Vitals.LocalHit; Explosive → ForceExplode
Damage / hit target selection: ProjectileManager.Hit uses ItemManager.Get(hit.collider) (collider → Item map). Creature.LocalHit:
- headshot if
transform.InverseTransformPoint(point).z > HeadPos→damage × GameInfo.HeadShotDamageMulti Server.HitCreature(this, player, damage, point, dir); hitmarker; if lethal locally → killscore (§7)
Melee (Melee.HitTarget) and fists (PlayerPunching.HitTarget) call the same Item.LocalHit(..., rangedHit:false).
Input: right mouse (PlayerRightClick) held → FishingRod.SecondaryInput → FishingRodCast.ReelOutInput
→ FishingRod.ReelOutInput: _isPullingBack = true, OnPullBackStarted (bail open)
rod rotation _curRodRot rises at _castingRodRotBackSpeed (FishingRod.RotateRod)
Release → ReelOutCancelled → OnPullBackReleased(_curRodRot) → _releasePoint
→ rod swings forward; UpdateReleasedPullBack: when _curRodRot/_releasePoint < _releaseBaitRotPercent → StartCast()
Cast : _isReelingOut = true; line length follows bait distance (ReelOut) up to _maxLineLength
Water : Bait.UnderwaterCheck (y < WaterManager.WaterHeight) → Bait.BaitsUnderWater, RandomizedCatchTime = Random(CatchTimeMinMax)
TimeUnderWater grows only while the bait moves horizontally (>√5 m/s) when BaitInfo.RequireReelingToCatch
Bite : [HOST] CreatureManager.TickUpdate → FindFishForBait(bait):
requires bait.y < _minBaitLevel (-2.5), no ServerItemOnBait, TimeUnderWater ≥ RandomizedCatchTime
→ GetRandomItem(pos, BaitInfo.ItemWeights) weighted random over Fishable.ItemToSpawn
→ HookItem(prefab, bait): Instantiate; Random(0,100) < _shinyCreatureChance → SetDrip();
item.SetAttachedRod(rod) (SyncVar) ; Spawn()
Hooked: Item.OnAttachedRodChange → InitializeBait → FixedJoint to bait; client UI "caught"
Reel : left mouse (PrimaryInput) → ReelInInput → StartReel(); FixedUpdate shortens line (_holdReelSpeed × _curReelSpeedMulti)
Land : right mouse while item on bait → FishingRod.ReleaseItem → Server.ReleaseItemFromBait → SetAttachedRod(null), bait may be consumed (LostOnBaitChance)
(also auto-release after 5 s above water: Bait.IncreaseAboveWaterTime)
Kill : any LocalHit (punch / melee / gun / explosion) → Creature dies → CanPickUp
Pickup: "PlayerPickUp" → PlayerHolding.PickUpInput → Item.PickUp(calledFromLocal, sendToServer)
Sell : [HOST] SellBox.OnTriggerStay (dead creature, not held) → MoneyManager.SellItem(item) (+TotalWorth)
or NPC money quest: NPC.EatItem → MoneyManager.SellItem + DestroyItem(NPC)
No line tension / line-break mechanic exists in this build (no tension variable, no break code). No Line Break / Infinite Line Strength therefore have nothing to act on.
| Attribute | Decided | Code |
|---|---|---|
| Species | On bite, host | CreatureManager.GetRandomItem (weights from current BaitInfo.ItemWeights) |
| Variant ("Drip"/shiny) | On bite, host | HookItem: Random.Range(0,100) < _shinyCreatureChance → Creature.SetDrip() (_isDrip SyncVar) |
| Weight | On network spawn, host | Creature.OnStartServer → CreatureUtils.GetWeightWithStandardDeviation(ObjectId): Gaussian(mean 1, σ 0.15) seeded by GameInfo.Seed + ObjectId, clamped 0.25–1.75 → _syncedRandomWeight |
| Value | Derived on demand | Item.TotalWorth (formula in §3) |
| Killscore multiplier | On kill, client→host | Server.SetItemMultiplier |
| Rarity | Not a fish attribute. enum Rarity {Default, Common, Rare, Legendary} is only used by ItemSkin (weapon/boat skins, slot machine). |
|
enum FishType |
Unused (no references) |
Displayed weight in-game = _weight × _syncedRandomWeight kg (Creature.InspectInput).
KillScoreCalculator (static list builder) — called in Creature.LocalHit on the local killing blow:
bonuses = rangedHit ? GetRangedBonuses(creature, point, damage) : GetMeleeBonuses(creature);
multiplier = product of Bonus.Worth; Server.SetItemMultiplier(item, multiplier); PlayerKillScore.AddKillScore(name, TotalWorth, bonuses).
| Bonus | Worth | Condition (exact) |
|---|---|---|
| Endangered | 1.25 | creature.IsEndangered |
| Double/Triple/Quadra/Penta/Multikill | lerp(1,1.5,n×0.1) | PlayerSkills.Multikill = kills within 3 s |
| 360 | 1.5 | PlayerSkills.RecentlyDid360: ≥320° accumulated yaw, gaps ≤0.5 s, within last 1 s |
| No-scope | 1.2 | not ADS and ≥0.25 s since leaving ADS |
| Quick-scope | 1.1 | ADS for ≤0.5 s |
| Longshot | 1.3 | distance(target, camera) ≥ 25 |
| Headshot | 1.25 | local z of hit point > HeadPos |
| Dogfight | 1.5 | player airborne and no ground within 1.5 m below target |
| Aerial | 1.25 | player airborne only |
| Fly fishing | 1.25 | target airborne only |
| One shot one kill | 1.25 | target at full HP and damage ≥ HP |
| Point blank | 1.1 | distance(target, player) < 2 |
| Overkill | 1.25 | HP − damage < −70 (not boss) |
| Last bullet | 1.25 | held weapon Ammo == 0 after the shot |
| Finally | 1.1 | Σ _timeBetweenShots since last kill > 5 |
| Killsteal | 1.3 | LocalHitsTaken == 0 and HP ≤ 30% |
| Noob kill | 1.01 | no other bonus |
| Impressive | 2.0 | ≥ 5 bonuses already |
| Melee | 1.05 | melee kill; Explosion 1.25 for explosions |
Note: CheckScope and CheckNoob fire achievement checks, so the mod predicts bonuses with its own side-effect-free copy of this logic.
- Movement: custom Rigidbody controller (
PlayerMovement, not CharacterController).Move()inFixedUpdaterewrites_rig.linearVelocityfrom_walkSpeed/_sprintSpeed,_acceleration,_decceleration,_extraGravityForce. Jump setsvelocity.y = _jumpForce;JumpInputallows jumping if grounded/coyote/swimming/touching boat orPlayerManager.InGodMode. - No fall damage exists in the game. Water damage: head underwater > 2 s →
Server.HitPlayer(_player, 10, …, damageType 0)every 0.5 s. - Vitals (host): health/fullness 0–100, poison & fire DoT, regen needs ≥80% fullness, invulnerability 0.25 s after damage.
- Camera:
PlayerCamera._rotEuler (pitch clamped ±90); recoil via_recoilTar/_recoilCur; FOV from ADS. - Interaction:
PlayerHoldingcapsule-overlap scan of_grabRange×_grabRadius; interactions are instant (no hold timers exist).
IslandManager/OnlineIslandManager(host_curIslandSyncVar,TpToSpecificIsland,TpToNextIsland);Island.IslandPos,SpawnManager.PlayerSpawnPos.BossManager.Boss(Creature),IsImmortal,BossMaxHp.NPCManager._idToNpc(NPCs are MonoBehaviours tagged "NPC"; shooting them only plays effects).BoatManager.Boat(VisualBoat,Driver,Velocity).WaterManager.WaterHeight.- Built-in dev tooling
DazedCommands(chat/spawn,/addmoney,/godmode,/oneshot, …) is gated byClientSettings.CheatsEnabled, which only a developer Steam ID can toggle (ButtonManager.CheatsButton→SteamManager.IsDev). The mod calls the same underlying APIs directly and never flipsCheatsEnabled(it would also enable dev hotkeys M/N/G/H/O/T and 99,999 starting money).