Loki icon indicating copy to clipboard operation
Loki copied to clipboard

Using Loki (Master, not 0.11-beta) on Mistlands release version, not PTB, causes fch file to have an error, yet backup, PTB upgraded saves work.

Open rjbprime opened this issue 3 years ago • 34 comments

Hello @Wufflez, I ran the Public Test Build when it initially came out for a bit, after following IronGate dev's backup, created a new character, played for about 10 minutes, then reverted back to the main, not PTB build, whilst reverting saves.

Today, after updating to the not-PTB build of Mistlands, I created a new character, grabbed the code for Loki from your Gihub, then compiled it. Upon loading the new character from today (not the one from two weeks ago), it give an error as shown in the attached image , yet it seems to load my two other saves, one being the PTB Mistlands character, the other being the one I've been playing up until about August this year, with no PTB or anything.

Not being a dot net 3 core coder, I am unsure on what to do.

attached image

rjbprime avatar Dec 07 '22 13:12 rjbprime

Are you using the latest code, another user has updated the program recently for the mistland stuff but I haven't released a compiled version for a while.

I haven't played the game in a long while (although the new mistland update is tempting me back! haha)

If you are still having issues, you could upload your save file and I could try to debug it here locally. I need to update my game though ideally and also decompile Valheim to have a look at what they've changed. I'm a bit out of the loop to be honest with you.

Wufflez avatar Dec 07 '22 18:12 Wufflez

All good. Before I git cloned this repo, i tried the other fork that had mistlands compatibility, it gave the same error. I then cleared the directory, cloned this one's master, worked out how to build, and ran the newly built exe to try the save, same error.

I'll attach the save to this as zip file, just rename it from "filename".zip to "filename".fch.

ragnar_backup_20221208-165954.zip

rjbprime avatar Dec 08 '22 07:12 rjbprime

the other fork that had mistlands compatibility

Which fork was the one you tried? (actually just looking for a mistlands fork of this repo)

flying4fun avatar Dec 12 '22 14:12 flying4fun

@flying4fun: Was referring to @bdomars patched version. I've tried that, it doesn't appear to work with save either.

rjbprime avatar Dec 13 '22 02:12 rjbprime

@rjbprime I've had a quick look at your save and I can see why it's not loading, it's due to hitting a Carapace Spear in your inventory. It seems the mistlands patch is not yet complete. I haven't yet played the mistalands update yet as I've been away from my PC for a while, but once I finish work for the holiday period I will have some time at my computer to patch in the missing item and any others that might be missing too. I'll release a new version then.

There are a few other things I need to address like the previous update too which added cloud saves and some built in backup mechanism, I should just swap Loki to use the same backup naming scheme and show legacy / cloud saves in the selection the same way as the game does.

I appologise for the delay, it's been a crazy month for me and difficult to find time for hobbies.

Thanks

Wufflez avatar Dec 13 '22 18:12 Wufflez

Gotcha, thanks. I tried @bdomars patched version and I too can verify it has issues if you have any of the new mistland items in your inventory. Otherwise, it works fine.

flying4fun avatar Dec 13 '22 23:12 flying4fun

Some new items that need to be added:

["Softtissue"] = new SharedItemData
            {
                ItemName = "Softtissue", IsTeleportable = true, UsesDurability = false, MaxDurability = 100,
                DurabilityPerLevel = 50, MaxStack = 40, DisplayName = "Soft tissue", MaxQuality = 1, ItemType = (ItemType)1,
            },
            ["Fish2"] = new SharedItemData
            {
                ItemName = "Fish2", IsTeleportable = true, UsesDurability = false, MaxDurability = 100,
                DurabilityPerLevel = 50, MaxStack = 10, DisplayName = "Pike", MaxQuality = 4, ItemType = (ItemType)1,
            },
            ["Fish1"] = new SharedItemData
            {
                ItemName = "Fish1", IsTeleportable = true, UsesDurability = false, MaxDurability = 100,
                DurabilityPerLevel = 50, MaxStack = 10, DisplayName = "Perch", MaxQuality = 2, ItemType = (ItemType)1,
            },
            ["FishingBaitForest"] = new SharedItemData
            {
                ItemName = "FishingBaitForest", IsTeleportable = true, UsesDurability = false, MaxDurability = 100,
                DurabilityPerLevel = 50, MaxStack = 100, DisplayName = "Mossy fishing bait",
                MaxQuality = 1, ItemType = (ItemType)9,
            },
            ["BowSpineSnap"] = new SharedItemData
            {
                ItemName = "BowSpineSnap", IsTeleportable = true, UsesDurability = true, MaxDurability = 100,
                DurabilityPerLevel = 50, MaxStack = 1, DisplayName = "Spine snap",
                MaxQuality = 2, ItemType = (ItemType)4,
            },
            ["ArrowCarapace"] = new SharedItemData
            {
                 ItemName = "ArrowCarapace", IsTeleportable = true, UsesDurability = false, MaxDurability = 100,
                 DurabilityPerLevel = 50, MaxStack = 100, DisplayName = "Carapace arrow",
                 MaxQuality = 1, ItemType = (ItemType)9
            },
            ["MushroomMagecap"] = new SharedItemData
            {
                ItemName = "MushroomMagecap", IsTeleportable = true, UsesDurability = false, MaxDurability = 100,
                DurabilityPerLevel = 50, MaxStack = 50, DisplayName = "Magecap",
                MaxQuality = 1, ItemType = (ItemType)2,
            },
            ["MushroomJotunpuffs"] = new SharedItemData
            {
                ItemName = "MushroomJotunpuffs", IsTeleportable = true, UsesDurability = false, MaxDurability = 100,
                DurabilityPerLevel = 50, MaxStack = 50, DisplayName = "Jotun puffs",
                MaxQuality = 1, ItemType = (ItemType)2,
            },
            ["ScaleHide"] = new SharedItemData
            {
                ItemName = "ScaleHide", IsTeleportable = true, UsesDurability = false, MaxDurability = 100,
                DurabilityPerLevel = 50, MaxStack = 50, DisplayName = "Scale hide",
                MaxQuality = 1, ItemType = (ItemType)1,
            }

I don't actually have the Carapace stuff so not 100% sure the max stack size, but the rest of info is correct.

New SkillType: Fishing = 104, SkillType.Fishing => Properties.Resources.Fishing,

Just breakpoint:

public static SharedItemData TryFindSharedData(string itemName) => 
            ItemData.TryGetValue(itemName, out SharedItemData sharedData) ? sharedData : null;

and see which items it returns null for. Those would be new items.

With the above changes, it works fine on Mistlands. I've only tested it on a local server but should be fine on any server actually.

Brandon-T avatar Dec 14 '22 00:12 Brandon-T

@Brandon-T : I thought there were several new fish, the crossbow skill, the eitr mage gear, and quite a few other things.

The Detailed Patch Notes for 0.212.7 over on steam says the folowing:

Valheim Patch Notes.txt

rjbprime avatar Dec 14 '22 02:12 rjbprime

@Brandon-T : I thought there were several new fish, the crossbow skill, the eitr mage gear, and quite a few other things.

The Detailed Patch Notes for 0.212.7 over on steam says the folowing:

Valheim Patch Notes.txt

I guess so, but it doesn't show up for me yet. I think they're unlocked so I probably have to do something in game to unlock it. However when I decompiled the valheim assembly, I did not see a crossbow skill in it, or a mage skill (eitr).

Brandon-T avatar Dec 14 '22 02:12 Brandon-T

The crossbow skill is separate from the bow skill, and is used for the new arbalest crossbow, and the eitr skills are Blood magic & Elemental magic. Blood Magic is increased with the use of the staff of protection or the Dead Riser, whereas the Elemental magic is raised by the Staves of frost or ember.

rjbprime avatar Dec 14 '22 02:12 rjbprime

All unlocked with mistlands resources and crafting tables.

rjbprime avatar Dec 14 '22 02:12 rjbprime

You're right. The new skills are (ElementalMagic [Previously known as FireMagic], BloodMagic [Previously known as Frost Magic], Crossbows, Fishing):

public enum SkillType
    {
        None = 0,
        Swords = 1,
        Knives = 2,
        Clubs = 3,
        Polearms = 4,
        Spears = 5,
        Blocking = 6,
        Axes = 7,
        Bows = 8,
        ElementalMagic = 9,
        BloodMagic = 10,
        Unarmed = 11,
        Pickaxes = 12,
        WoodCutting = 13,
        Crossbows = 14,
        Jump = 100,
        Sneak = 101,
        Run = 102,
        Swim = 103,
        Fishing = 104,
        Ride = 110,
        All = 999
    }

Brandon-T avatar Dec 14 '22 02:12 Brandon-T

Oh, my PR was merged 😄 I've been away for a while so didn't notice before.

But yes as you seem to have figured out the updates I did were not actually enough to work with Mistlands, merely they changed the reading and writing of bytes to allow for editing but all new Mistlands content is still missing from the editor and makes it crash even though the savefile bytes are read in the correct order.

Did you @Brandon-T already make changes you'd like to contribute?

I've been looking for a way to exctract the missing data from the game itself but haven't figured that out yet, although I think @Wufflez already has a way of doing that?

bdomars avatar Dec 17 '22 19:12 bdomars

Oh, my PR was merged 😄 I've been away for a while so didn't notice before.

But yes as you seem to have figured out the updates I did were not actually enough to work with Mistlands, merely they changed the reading and writing of bytes to allow for editing but all new Mistlands content is still missing from the editor and makes it crash even though the savefile bytes are read in the correct order.

Did you @Brandon-T already make changes you'd like to contribute?

I've been looking for a way to exctract the missing data from the game itself but haven't figured that out yet, although I think @Wufflez already has a way of doing that?

I dumped all items from the game into a list: Valheim.txt

I don't actually know if I broke any item names or anything as I did not test it. I don't feel like updating the project to .NET 6 from .NET 3, and compiling it. But I was interested in what items the game has and how the game works, so I dumped it. Hopefully the list helps.

Wrote code to dump it:

#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>

enum class ItemType {
	None = 0,
	Material = 1,
	Consumable = 2,
	OneHandedWeapon = 3,
	Bow = 4,
	Shield = 5,
	Helmet = 6,
	Chest = 7,
	Ammo = 9,
	Customization = 10,
	Legs = 11,
	Hands = 12,
	Trophy = 13,
	TwoHandedWeapon = 14,
	Torch = 15,
	Misc = 16,
	Shoulder = 17,
	Utility = 18,
	Tool = 19,
	AttachAtgeir = 20,
	Fish = 21,
	TwoHandedWeaponLeft = 22,
	AmmoNonEquipable = 23,
};

struct ItemData {
	std::string item_name;
	bool is_teleportable;
	bool uses_durability;
	double max_durability;
	double durability_per_level;
	int max_stack;
	std::string display_name;
	int max_quality;
	ItemType item_type;
};

std::string to_string(const ItemType& type)
{
	switch (type)
	{
	case ItemType::None: return "None";
	case ItemType::Material: return "Material";
	case ItemType::Consumable: return "Consumable";
	case ItemType::OneHandedWeapon: return "OneHandedWeapon";
	case ItemType::Bow: return "Bow";
	case ItemType::Shield: return "Shield";
	case ItemType::Helmet: return "Helmet";
	case ItemType::Chest: return "Chest";
	case ItemType::Ammo: return "Ammo";
	case ItemType::Customization: return "Customization";
	case ItemType::Legs: return "Legs";
	case ItemType::Hands: return "Hands";
	case ItemType::Trophy: return "Trophy";
	case ItemType::TwoHandedWeapon: return "TwoHandedWeapon";
	case ItemType::Torch: return "Torch";
	case ItemType::Misc: return "Misc";
	case ItemType::Shoulder: return "Shoulder";
	case ItemType::Utility: return "Utility";
	case ItemType::Tool: return "Tool";
	case ItemType::AttachAtgeir: return "AttachAtgeir";
	case ItemType::Fish: return "Fish";
	case ItemType::TwoHandedWeaponLeft: return "TwoHandedWeaponLeft";
	case ItemType::AmmoNonEquipable: return "AmmoNonEquipable";
	}
}

std::string trim(const std::string& str)
{
	std::string s = str;
	std::string white_space;
	for (int i = 0; i < 128; ++i) {
		if (i > 32 && i <= 126) {
			continue;
		}

		white_space += static_cast<char>(i);
	}


	s.erase(s.find_last_not_of(white_space) + 1);
	s.erase(0, s.find_first_not_of(white_space));
	return s;
}

bool parse(const std::string& line, const std::string& id, std::string& out)
{
	try {
		std::size_t offset = line.find(id);
		if (offset != std::string::npos)
		{
			out = trim(line.substr(offset + id.length()));
			return true;
		}
	}
	catch (const std::exception& e) {
		std::cerr << "INVALID PARSE: " + id + " with: " + line << "\n";
		throw e;
	}
	return false;
}

int main()
{
	std::vector<ItemData> items;

	std::fstream localizations{ std::filesystem::path("./Assets/Resources/localization.txt"), std::ios::in };
	if (!localizations)
	{
		return -1;
	}

	localizations.seekg(0, std::ios::end);
	std::string locale;
	locale.resize(localizations.tellg());
	localizations.seekg(0, std::ios::beg);
	localizations.read(&locale[0], locale.size());

	const std::filesystem::path path = "./Assets/PrefabInstance";
	for (auto const& dir_entry : std::filesystem::directory_iterator(path)) {
		if (dir_entry.path().extension() == ".prefab") {
			std::fstream file{dir_entry.path(), std::ios::in};
			if (file) {
				file.seekg(0, std::ios::end);
				std::size_t size = file.tellg();
				file.seekg(0, std::ios::beg);

				bool found_item = false;
				std::string str;
				while (std::getline(file, str)) {
					if (str.find("m_itemData") != std::string::npos) {
						found_item = true;
						break;
					}
				}

				if (found_item) {
					file.seekg(0, std::ios::beg);
					str.resize(size);
					file.read(&str[0], size);

					std::stringstream ss(str);
					std::size_t offset = str.find("m_itemData");
					ss.seekg(offset, std::ios::beg);

					std::string line;
					ItemData item_data = {};

					while (std::getline(ss, line)) {
						if (line.find("---") == 0) {
							break;
						}

						std::string value;
						if (parse(line, "m_name:", value))
						{
							item_data.display_name = value;
						}
						else if (parse(line, "m_teleportable:", value))
						{
							item_data.is_teleportable = std::atoi(value.c_str());
						}
						else if (parse(line, "m_useDurability:", value))
						{
							item_data.uses_durability = std::atoi(value.c_str());
						}
						else if (parse(line, "m_maxDurability:", value))
						{
							item_data.max_durability = std::atof(value.c_str());
						}
						else if (parse(line, "m_durabilityPerLevel:", value))
						{
							item_data.durability_per_level = std::atof(value.c_str());
						}
						else if (parse(line, "m_maxStackSize:", value))
						{
							item_data.max_stack = std::atoi(value.c_str());
						}
						else if (parse(line, "m_maxQuality:", value))
						{
							item_data.max_quality = std::atoi(value.c_str());
						}
						else if (parse(line, "m_itemType:", value))
						{
							int type = std::atoi(value.c_str());
							if (type >= 0 || type <= static_cast<int>(ItemType::AmmoNonEquipable))
							{
								item_data.item_type = static_cast<ItemType>(std::atoi(value.c_str()));
							}
							else
							{
								throw std::runtime_error("Invalid Item Type: " + std::to_string(type));
							}
						}
					}

					ss = std::stringstream(str);
					ss.seekg(0, std::ios::beg);
					while (std::getline(ss, line))
					{
						std::string value;
						if (parse(line, "m_Name:", value))
						{
							item_data.item_name = value;
							break;
						}
					}

					if (!item_data.display_name.empty()) {
						std::string item_locale_key = "\"" + item_data.display_name.substr(1) + "\",\"";
						offset = locale.find(item_locale_key);
						if (offset != std::string::npos)
						{
							offset += item_locale_key.size();

							std::size_t offset2 = locale.find("\",", offset);
							if (offset2 != std::string::npos)
							{
								item_data.display_name = locale.substr(offset, offset2 - offset);
							}
						}
					}
					else {
						item_data.display_name = "$" + item_data.item_name;
					}

					items.push_back(item_data);
				}
			}
		}
	}

	std::fstream output{ "~/Desktop/Valheim.txt", std::ios::out };
	for (const auto& item_data : items) {
		/*std::cout << "Item: " << item_data.item_name << "\n";
		std::cout << "  is_teleportable: " << (item_data.is_teleportable ? "true" : "false") << "\n";
		std::cout << "  uses_durability: " << (item_data.uses_durability ? "true" : "false") << "\n";
		std::cout << "  max_durability: " << item_data.max_durability << "\n";
		std::cout << "  durability_per_level: " << item_data.durability_per_level << "\n";
		std::cout << "  max_stack: " << item_data.max_stack << "\n";
		std::cout << "  display_name: " << item_data.display_name << "\n";
		std::cout << "  max_quality: " << item_data.max_quality << "\n";
		std::cout << "  item_type: " << to_string(item_data.item_type) << "\n";*/

		std::stringstream ss;
		ss << "[\"" << item_data.item_name << "\"]";
		ss << " = new SharedItemData\n";
		ss << "{\n";
		ss << "\tItemName = " << "\"" << item_data.item_name << "\", IsTeleportable = " << (item_data.is_teleportable ? "true" : "false") << ", UsesDurability = " << (item_data.uses_durability ? "true" : "false") << ", MaxDurability = " << item_data.max_durability << ",\n";
		ss << "\tDurabilityPerLevel = " << item_data.durability_per_level << ", MaxStack = " << item_data.max_stack << ", DisplayName = \"" << item_data.display_name << "\",\n";
		ss << "\tMaxQuality = " << item_data.max_quality << ", ItemType = ItemType." << to_string(item_data.item_type) << ",\n";
		ss << "},\n";

		output << ss.str();
	}
	return 0;
}

The game's prefab format is (m_Name is the Item-ID, and m_name is the localized item name [display name], m_itemData is the item info):

GameObject:
  serializedVersion: 6
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_Component:
  - component: {fileID: 4549847827358516}
  m_Layer: 12
  m_Name: Amber
  m_TagString: Untagged
  m_Icon: {fileID: 0}
  m_NavMeshLayer: 0
  m_StaticEditorFlags: 0
  m_IsActive: 1
--- !u!4 &4549847827358516
MonoBehaviour:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 1984232819277963}
  m_Enabled: 1
  m_EditorHideFlags: 0
  m_Script: {fileID: 11500000, guid: 4c735e3b6058d7147ada1f43d98ece79, type: 3}
  m_Name:
  m_EditorClassIdentifier:
  m_autoPickup: 1
  m_autoDestroy: 1
  m_itemData:
    m_stack: 1
    m_durability: 100
    m_quality: 1
    m_variant: 0
    m_shared:
      m_name: $item_amber
      m_dlc:
      m_itemType: 1
      m_icons:
      - {fileID: 21300000, guid: 9c931644b863c1e4aadeec2f9f2924b5, type: 2}
      m_attachOverride: 0
      m_description: $item_amber_description
      m_maxStackSize: 20
      m_autoStack: 1
      m_maxQuality: 1
      m_scaleByQuality: 0
      m_weight: 0.1
      m_scaleWeightByQuality: 0
      m_value: 5
      m_teleportable: 1
      m_questItem: 0
      m_equipDuration: 1
      m_variants: 0
      m_trophyPos: {x: 0, y: 0}
      m_buildPieces: {fileID: 0}
      m_centerCamera: 0
      m_setName:
      m_setSize: 0
      m_setStatusEffect: {fileID: 0}
      m_equipStatusEffect: {fileID: 0}
      m_movementModifier: 0
      m_eitrRegenModifier: 0
      m_food: 0
      m_foodStamina: 0
      m_foodEitr: 0
      m_foodBurnTime: 0
      m_foodRegen: 0
      m_armorMaterial: {fileID: 0}
      m_helmetHideHair: 1
      m_helmetHideBeard: 0
      m_armor: 10
      m_armorPerLevel: 1
      m_damageModifiers: []
      m_blockPower: 10
      m_blockPowerPerLevel: 0
      m_deflectionForce: 0
      m_deflectionForcePerLevel: 0
      m_timedBlockBonus: 1.5
      m_animationState: 1
      m_skillType: 1
      m_toolTier: 0
      m_damages:
        m_damage: 0
        m_blunt: 0
        m_slash: 0
        m_pierce: 0
        m_chop: 0
        m_pickaxe: 0
        m_fire: 0
        m_frost: 0
        m_lightning: 0
        m_poison: 0
        m_spirit: 0
      m_damagesPerLevel:
        m_damage: 0
        m_blunt: 0
        m_slash: 0
        m_pierce: 0
        m_chop: 0
        m_pickaxe: 0
        m_fire: 0
        m_frost: 0
        m_lightning: 0
        m_poison: 0
        m_spirit: 0
      m_attackForce: 50
      m_backstabBonus: 4
      m_dodgeable: 0
      m_blockable: 0
      m_tamedOnly: 0
      m_alwaysRotate: 0
      m_attackStatusEffect: {fileID: 0}
      m_spawnOnHit: {fileID: 0}
      m_spawnOnHitTerrain: {fileID: 0}
      m_projectileToolTip: 1
      m_ammoType:
      m_attack:
        m_attackType: 0
        m_attackAnimation:
        m_attackRandomAnimations: 0
        m_attackChainLevels: 0
        m_loopingAttack: 0
        m_consumeItem: 0
        m_hitTerrain: 1
        m_attackStamina: 20
        m_attackEitr: 0
        m_attackHealth: 0
        m_attackHealthPercentage: 0
        m_speedFactor: 0.2
        m_speedFactorRotation: 0.2
        m_attackStartNoise: 10
        m_attackHitNoise: 30
        m_damageMultiplier: 1
        m_forceMultiplier: 1
        m_staggerMultiplier: 1
        m_recoilPushback: 0
        m_selfDamage: 0
        m_attackOriginJoint:
        m_attackRange: 2.4
        m_attackHeight: 0.8
        m_attackOffset: 0
        m_spawnOnTrigger: {fileID: 0}
        m_toggleFlying: 0
        m_attach: 0
        m_requiresReload: 0
        m_reloadAnimation:
        m_reloadTime: 2
        m_reloadStaminaDrain: 0
        m_bowDraw: 0
        m_drawDurationMin: 0
        m_drawStaminaDrain: 0
        m_drawAnimationState:
        m_attackAngle: 90
        m_attackRayWidth: 0
        m_maxYAngle: 0
        m_lowerDamagePerHit: 1
        m_hitPointtype: 0
        m_hitThroughWalls: 0
        m_multiHit: 1
        m_pickaxeSpecial: 0
        m_lastChainDamageMultiplier: 2
        m_resetChainIfHit: 0
        m_raiseSkillAmount: 1
        m_skillHitType: 4
        m_specialHitSkill: 0
        m_specialHitType: 0
        m_attackProjectile: {fileID: 0}
        m_projectileVel: 10
        m_projectileVelMin: 2
        m_projectileAccuracy: 10
        m_projectileAccuracyMin: 20
        m_skillAccuracy: 0
        m_useCharacterFacing: 0
        m_useCharacterFacingYAim: 0
        m_launchAngle: 0
        m_projectiles: 1
        m_projectileBursts: 1
        m_burstInterval: 0
        m_destroyPreviousProjectile: 0
        m_perBurstResourceUsage: 0
        m_hitEffect:
          m_effectPrefabs: []
        m_hitTerrainEffect:
          m_effectPrefabs: []
        m_startEffect:
          m_effectPrefabs: []
        m_triggerEffect:
          m_effectPrefabs: []
        m_trailStartEffect:
          m_effectPrefabs: []
        m_burstEffect:
          m_effectPrefabs: []
      m_secondaryAttack:
        m_attackType: 0
        m_attackAnimation:
        m_attackRandomAnimations: 0
        m_attackChainLevels: 0
        m_loopingAttack: 0
        m_consumeItem: 0
        m_hitTerrain: 1
        m_attackStamina: 20
        m_attackEitr: 0
        m_attackHealth: 0
        m_attackHealthPercentage: 0
        m_speedFactor: 0.2
        m_speedFactorRotation: 0.2
        m_attackStartNoise: 10
        m_attackHitNoise: 30
        m_damageMultiplier: 1
        m_forceMultiplier: 1
        m_staggerMultiplier: 1
        m_recoilPushback: 0
        m_selfDamage: 0
        m_attackOriginJoint:
        m_attackRange: 1.5
        m_attackHeight: 0.6
        m_attackOffset: 0
        m_spawnOnTrigger: {fileID: 0}
        m_toggleFlying: 0
        m_attach: 0
        m_requiresReload: 0
        m_reloadAnimation:
        m_reloadTime: 2
        m_reloadStaminaDrain: 0
        m_bowDraw: 0
        m_drawDurationMin: 0
        m_drawStaminaDrain: 0
        m_drawAnimationState:
        m_attackAngle: 90
        m_attackRayWidth: 0
        m_maxYAngle: 0
        m_lowerDamagePerHit: 1
        m_hitPointtype: 0
        m_hitThroughWalls: 0
        m_multiHit: 1
        m_pickaxeSpecial: 0
        m_lastChainDamageMultiplier: 2
        m_resetChainIfHit: 0
        m_raiseSkillAmount: 1
        m_skillHitType: 4
        m_specialHitSkill: 0
        m_specialHitType: 0
        m_attackProjectile: {fileID: 0}
        m_projectileVel: 10
        m_projectileVelMin: 2
        m_projectileAccuracy: 10
        m_projectileAccuracyMin: 20
        m_skillAccuracy: 0
        m_useCharacterFacing: 0
        m_useCharacterFacingYAim: 0
        m_launchAngle: 0
        m_projectiles: 1
        m_projectileBursts: 1
        m_burstInterval: 0
        m_destroyPreviousProjectile: 0
        m_perBurstResourceUsage: 0
        m_hitEffect:
          m_effectPrefabs: []
        m_hitTerrainEffect:
          m_effectPrefabs: []
        m_startEffect:
          m_effectPrefabs: []
        m_triggerEffect:
          m_effectPrefabs: []
        m_trailStartEffect:
          m_effectPrefabs: []
        m_burstEffect:
          m_effectPrefabs: []
      m_useDurability: 0
      m_destroyBroken: 1
      m_canBeReparied: 1
      m_maxDurability: 100
      m_durabilityPerLevel: 50
      m_useDurabilityDrain: 1
      m_durabilityDrain: 0
      m_aiAttackRange: 2
      m_aiAttackRangeMin: 0
      m_aiAttackInterval: 2
      m_aiAttackMaxAngle: 5
      m_aiWhenFlying: 1
      m_aiWhenFlyingAltitudeMin: 0
      m_aiWhenFlyingAltitudeMax: 999999
      m_aiWhenWalking: 1
      m_aiWhenSwiming: 1
      m_aiPrioritized: 0
      m_aiInDungeonOnly: 0
      m_aiInMistOnly: 0
      m_aiMaxHealthPercentage: 1
      m_aiTargetType: 0
      m_hitEffect:
        m_effectPrefabs: []
      m_hitTerrainEffect:
        m_effectPrefabs: []
      m_blockEffect:
        m_effectPrefabs: []
      m_startEffect:
        m_effectPrefabs: []
      m_holdStartEffect:
        m_effectPrefabs: []
      m_triggerEffect:
        m_effectPrefabs: []
      m_trailStartEffect:
        m_effectPrefabs: []
      m_consumeStatusEffect: {fileID: 0}

Dumped the assets as well :) Was fun.

Brandon-T avatar Dec 21 '22 22:12 Brandon-T

On a related note, has the new hair and beard styles been enabled for character choice? I wasn't sure if those would also conflict with the save editor, I'd thought I'd mention them.

  • 9 new hairstyles and 7 new beard styles

rjbprime avatar Dec 22 '22 05:12 rjbprime

I dumped all items from the game into a list: Valheim.txt

Nice! I'll use that to update my PR that decouple these items from code (making it easy to update db of Loki in the future).

A thought: One could consider using your code though, and make Loki read the items from the actual game automatically. That would make it completely obsolete to have a local copy, in code or decoupled, of the item db. On the other hand, doing so will make Loki much more reliable on the prefab structure and harder to maintain. A separate db might be a better option.

On a related note, has the new hair and beard styles been enabled for character choice? I wasn't sure if those would also conflict with the save editor, I'd thought I'd mention them.

There are alot of things introduced in Mistlands update, that needs to go into Loki. Think we've got all items covered here, skills as well, but yes, hair and beard styles will have to be updated too (I haven't seen if they already have been?).

jensbrak avatar Dec 22 '22 09:12 jensbrak

@Brandon-T - what toolchain do you use to compile it with? My C++ is 25 years old :/ (from gamedev work actually!)

jensbrak avatar Dec 22 '22 09:12 jensbrak

For fun, I converted the list of items you extracted @Brandon-T in my version of Loki. However, there are clearly things in the list that is not player items. They can be added to inventory in Loki but the game does not work with them, inventory slots gets locked and equipping them results in message that DLC is required. In other words, in order to make use of an extracted list we need to filter out only player items from it.

jensbrak avatar Dec 22 '22 11:12 jensbrak

For fun, I converted the list of items you extracted @Brandon-T in my version of Loki. However, there are clearly things in the list that is not player items. They can be added to inventory in Loki but the game does not work with them, inventory slots gets locked and equipping them results in message that DLC is required. In other words, in order to make use of an extracted list we need to filter out only player items from it.

Yes. That is correct. If you do have DLCs (in the future I guess), then you can equip such items that require those DLCs. I can filter out those with the isDLC flag from the prefabs. For "other" items I'm not sure but should be able to filter by type I think. But yeah, the above list contains EVERYTHING, and I didn't test it. Only a raw dump.

I am using c++17 above because it uses <filesystem> header. Any tool chain should work (VS, GCC, Clang).

I used VS on windows for the above and Clang on MacOS. Both compile it :)

Yes there are NPC items in the list. I believe the original code also contains these. For example "wolf bite" is currently in the main branch. I can try to filter them I guess. Will have to see how the game does it. But on a local server if you run devcommands, it does have items that can't be spawned when you run "spawn" command.

I think the above list also contains the beards but I'd have to check tbh.

Brandon-T avatar Dec 22 '22 14:12 Brandon-T

I use VS but failed, need to brush off my skills there I think.

As for NPC items: already now (current version of Loki) there are items in the SharedItemData DB that Loki use, that makes no sense in being able to add to inventory. There has to be some mechanism or data in the game that distinguish NPC items from others. The prefab list is a mix of various items; some used for character look, some for inventory items and some for NPC characters.

As for beards, yes, they are in that list but to actually customize a character to have a beard, the list is not enough. Some of the items in the list are added as beards (trophys actually) for Loki, but the actual beards are hard coded and not read from the SharedItemData DB. So in order to use Loki to change appearance (First tab, ie "General") some more changes has to be added to Loki. I'll see if I can look into that too.

Update: I fixed a version of Loki that support the new beard and hair styles from Mistlands update. Currently in my own branch for it, I'll wait with the PR until we've sorted out the NPC question.

Also worth mentioning: https://github.com/Wufflez/Loki/pull/35 would prevent the crash described in this issue. Well, upcoming PR with Mistlands items I work on would also solve the crash (since it is caused by missing items). However, I believe the bugfix is still a good idea, since Valheim eventually will be updated with even more items that Loki will not support until updated. With the fix, at least it won't crash until being updated.

jensbrak avatar Dec 22 '22 14:12 jensbrak

Item List filtered by localization and m_Name. If it can't be localized and doesn't have a name, it's not included (all items in the game are localized for a ton of languages, so if it's not localized, it's an attack power of some NPC). This means, things like Abomination_attack1 would not be in the list!

However, items like Cape of Odin is there, but it has the m_dlc: beta flag so you MUST have played the beta to own such an item in the first place. Items with ItemType::Customization is not in the list as they aren't items or trophies, but they are customizations for the main-screen during character creation. We could possibly filter out more based on ItemType as well. Will need to check further though, and possibly actually compile Loki to see the issues everyone is having.

Updated Item List:

Valheim.txt

Diff (Changes from the last list I posted): https://www.diffchecker.com/KG4EbSAE

Note: I don't actually use Loki, I just like the reverse engineering that went into it :). So use the list, but beware that it MIGHT not be perfect. If you see any more issues with it, let me know. This list can probably help out the valheim wiki as well which is missing a ton of info on the new items.

I can try to update and compile Loki with .NET 6, and see how it works later on.

Brandon-T avatar Dec 22 '22 16:12 Brandon-T

Note: I don't actually use Loki, I just like the reverse engineering that went into it

TBH, that's precisely my take on it too and how I found Loki in the first place. I was digging into reverse engineer parts of the game, just for the fun of it. Now I can't resist making some PR's to Loki just because... well... I can.

I'll update my list with filtered list. So basically, if prefab items are localized - they're player items, is that what you're saying?

jensbrak avatar Dec 22 '22 17:12 jensbrak

Note: I don't actually use Loki, I just like the reverse engineering that went into it

TBH, that's precisely my take on it too and how I found Loki in the first place. I was digging into reverse engineer parts of the game, just for the fun of it. Now I can't resist making some PR's to Loki just because... well... I can.

I'll update my list with filtered list. So basically, if prefab items are localized - they're player items, is that what you're saying?

Yeah that's correct. Below in the updated dumper code, I added continue statements so that it doesn't add it to the list of valid items.

#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>

enum class ItemType {
	None = 0,
	Material = 1,
	Consumable = 2,
	OneHandedWeapon = 3,
	Bow = 4,
	Shield = 5,
	Helmet = 6,
	Chest = 7,
	Ammo = 9,
	Customization = 10,
	Legs = 11,
	Hands = 12,
	Trophy = 13,
	TwoHandedWeapon = 14,
	Torch = 15,
	Misc = 16,
	Shoulder = 17,
	Utility = 18,
	Tool = 19,
	AttachAtgeir = 20,
	Fish = 21,
	TwoHandedWeaponLeft = 22,
	AmmoNonEquipable = 23,
};

struct ItemData {
	std::string item_name;
	bool is_teleportable;
	bool uses_durability;
	double max_durability;
	double durability_per_level;
	int max_stack;
	std::string display_name;
	int max_quality;
	ItemType item_type;
};

std::string to_string(const ItemType& type)
{
	switch (type)
	{
	case ItemType::None: return "None";
	case ItemType::Material: return "Material";
	case ItemType::Consumable: return "Consumable";
	case ItemType::OneHandedWeapon: return "OneHandedWeapon";
	case ItemType::Bow: return "Bow";
	case ItemType::Shield: return "Shield";
	case ItemType::Helmet: return "Helmet";
	case ItemType::Chest: return "Chest";
	case ItemType::Ammo: return "Ammo";
	case ItemType::Customization: return "Customization";
	case ItemType::Legs: return "Legs";
	case ItemType::Hands: return "Hands";
	case ItemType::Trophy: return "Trophy";
	case ItemType::TwoHandedWeapon: return "TwoHandedWeapon";
	case ItemType::Torch: return "Torch";
	case ItemType::Misc: return "Misc";
	case ItemType::Shoulder: return "Shoulder";
	case ItemType::Utility: return "Utility";
	case ItemType::Tool: return "Tool";
	case ItemType::AttachAtgeir: return "AttachAtgeir";
	case ItemType::Fish: return "Fish";
	case ItemType::TwoHandedWeaponLeft: return "TwoHandedWeaponLeft";
	case ItemType::AmmoNonEquipable: return "AmmoNonEquipable";
	}
}

std::string trim(const std::string& str)
{
	std::string s = str;
	std::string white_space;
	for (int i = 0; i < 128; ++i) {
		if (i > 32 && i <= 126) {
			continue;
		}

		white_space += static_cast<char>(i);
	}


	s.erase(s.find_last_not_of(white_space) + 1);
	s.erase(0, s.find_first_not_of(white_space));
	return s;
}

bool parse(const std::string& line, const std::string& id, std::string& out)
{
	try {
		std::size_t offset = line.find(id);
		if (offset != std::string::npos)
		{
			out = trim(line.substr(offset + id.length()));
			return true;
		}
	}
	catch (const std::exception& e) {
		std::cerr << "INVALID PARSE: " + id + " with: " + line << "\n";
		throw e;
	}
	return false;
}

int main()
{
	std::vector<ItemData> items;

	std::fstream localizations{ std::filesystem::path("./Assets/Resources/localization.txt"), std::ios::in };
	if (!localizations)
	{
		return -1;
	}

	localizations.seekg(0, std::ios::end);
	std::string locale;
	locale.resize(localizations.tellg());
	localizations.seekg(0, std::ios::beg);
	localizations.read(&locale[0], locale.size());

	const std::filesystem::path path = "./Assets/PrefabInstance";
	for (auto const& dir_entry : std::filesystem::directory_iterator(path)) {
		if (dir_entry.path().extension() == ".prefab") {
			std::fstream file{dir_entry.path(), std::ios::in};
			if (file) {
				file.seekg(0, std::ios::end);
				std::size_t size = file.tellg();
				file.seekg(0, std::ios::beg);

				bool found_item = false;
				std::string str;
				while (std::getline(file, str)) {
					if (str.find("m_itemData") != std::string::npos) {
						found_item = true;
						break;
					}
				}

				if (found_item) {
					file.seekg(0, std::ios::beg);
					str.resize(size);
					file.read(&str[0], size);

					std::stringstream ss(str);
					std::size_t offset = str.find("m_itemData");
					ss.seekg(offset, std::ios::beg);

					std::string line;
					ItemData item_data = {};

					while (std::getline(ss, line)) {
						if (line.find("---") == 0) {
							break;
						}

						std::string value;
						if (parse(line, "m_name:", value))
						{
							item_data.display_name = value;
						}
						else if (parse(line, "m_teleportable:", value))
						{
							item_data.is_teleportable = std::atoi(value.c_str());
						}
						else if (parse(line, "m_useDurability:", value))
						{
							item_data.uses_durability = std::atoi(value.c_str());
						}
						else if (parse(line, "m_maxDurability:", value))
						{
							item_data.max_durability = std::atof(value.c_str());
						}
						else if (parse(line, "m_durabilityPerLevel:", value))
						{
							item_data.durability_per_level = std::atof(value.c_str());
						}
						else if (parse(line, "m_maxStackSize:", value))
						{
							item_data.max_stack = std::atoi(value.c_str());
						}
						else if (parse(line, "m_maxQuality:", value))
						{
							item_data.max_quality = std::atoi(value.c_str());
						}
						else if (parse(line, "m_itemType:", value))
						{
							int type = std::atoi(value.c_str());
							if (type >= 0 || type <= static_cast<int>(ItemType::AmmoNonEquipable))
							{
								item_data.item_type = static_cast<ItemType>(std::atoi(value.c_str()));
							}
							else
							{
								throw std::runtime_error("Invalid Item Type: " + std::to_string(type));
							}
						}
					}

					if (item_data.item_type == ItemType::Customization) {
						continue;  // Customizations like Beard, Hair, etc aren't items.
					}

					ss = std::stringstream(str);
					ss.seekg(0, std::ios::beg);
					while (std::getline(ss, line))
					{
						std::string value;
						if (parse(line, "m_Name:", value))
						{
							item_data.item_name = value;
							break;
						}
					}

					if (!item_data.display_name.empty()) {
						std::string item_locale_key = "\"" + item_data.display_name.substr(1) + "\",\"";
						offset = locale.find(item_locale_key);
						if (offset != std::string::npos)
						{
							offset += item_locale_key.size();

							std::size_t offset2 = locale.find("\",", offset);
							if (offset2 != std::string::npos)
							{
								item_data.display_name = locale.substr(offset, offset2 - offset);
							}
						}
						else
						{
							continue; // Cannot be localized, so not an item
						}
					}
					else {
						item_data.display_name = "$" + item_data.item_name;
						continue; // No display name, so not an item
					}

					items.push_back(item_data);
				}
			}
		}
	}

	std::fstream output{ "~/Desktop/Valheim.txt", std::ios::out };
	for (const auto& item_data : items) {
		/*std::cout << "Item: " << item_data.item_name << "\n";
		std::cout << "  is_teleportable: " << (item_data.is_teleportable ? "true" : "false") << "\n";
		std::cout << "  uses_durability: " << (item_data.uses_durability ? "true" : "false") << "\n";
		std::cout << "  max_durability: " << item_data.max_durability << "\n";
		std::cout << "  durability_per_level: " << item_data.durability_per_level << "\n";
		std::cout << "  max_stack: " << item_data.max_stack << "\n";
		std::cout << "  display_name: " << item_data.display_name << "\n";
		std::cout << "  max_quality: " << item_data.max_quality << "\n";
		std::cout << "  item_type: " << to_string(item_data.item_type) << "\n";*/

		std::stringstream ss;
		ss << "[\"" << item_data.item_name << "\"]";
		ss << " = new SharedItemData\n";
		ss << "{\n";
		ss << "\tItemName = " << "\"" << item_data.item_name << "\", IsTeleportable = " << (item_data.is_teleportable ? "true" : "false") << ", UsesDurability = " << (item_data.uses_durability ? "true" : "false") << ", MaxDurability = " << item_data.max_durability << ",\n";
		ss << "\tDurabilityPerLevel = " << item_data.durability_per_level << ", MaxStack = " << item_data.max_stack << ", DisplayName = \"" << item_data.display_name << "\",\n";
		ss << "\tMaxQuality = " << item_data.max_quality << ", ItemType = ItemType." << to_string(item_data.item_type) << ",\n";
		ss << "},\n";

		output << ss.str();
	}
	return 0;
}

If we copy the items that were removed from the list, we can get the beards and hairs:

private static readonly Beard[] SensibleBeards = 
{
    new Beard(Loki.Properties.Resources.B_No_beard, "BeardNone"),
    new Beard(Loki.Properties.Resources.B_Braided_2, "Beard5"),
    new Beard(Loki.Properties.Resources.B_Braided_2, "Beard6"),
    new Beard(Loki.Properties.Resources.B_Braided_3, "Beard9"),
    new Beard(Loki.Properties.Resources.B_Braided_4, "Beard10"),
    new Beard(Loki.Properties.Resources.B_Thick_2, "Beard11"),
    new Beard(Loki.Properties.Resources.B_Royal_1, "Beard12"),
    new Beard(Loki.Properties.Resources.B_Royal_2, "Beard13"),
    new Beard(Loki.Properties.Resources.B_Braided_5, "Beard14"),
    new Beard(Loki.Properties.Resources.B_Short_4, "Beard15"),
    new Beard(Loki.Properties.Resources.B_Stonedweller, "Beard16"),
    new Beard(Loki.Properties.Resources.B_Long_1, "Beard1"),
    new Beard(Loki.Properties.Resources.B_Long_2, "Beard2"),
    new Beard(Loki.Properties.Resources.B_Short_1, "Beard3"),
    new Beard(Loki.Properties.Resources.B_Short_2, "Beard4"),
    new Beard(Loki.Properties.Resources.B_Short_3, "Beard7"),
    new Beard(Loki.Properties.Resources.B_Thick_1, "Beard8"),
};

private static readonly Hair[] SensibleHairs =
{
    new Hair(Loki.Properties.Resources.No_hair, "HairNone"),
    new Hair(Loki.Properties.Resources.Braided_1, "Hair3"),
    new Hair(Loki.Properties.Resources.Braided_2, "Hair11"),
    new Hair(Loki.Properties.Resources.Braided_3, "Hair12"),
    new Hair(Loki.Properties.Resources.Long_1, "Hair6"),
    new Hair(Loki.Properties.Resources.Ponytail_1, "Hair1"),
    new Hair(Loki.Properties.Resources.Ponytail_2, "Hair2"),
    new Hair(Loki.Properties.Resources.Ponytail_3, "Hair4"),
    new Hair(Loki.Properties.Resources.Ponytail_4, "Hair7"),
    new Hair(Loki.Properties.Resources.Short_1, "Hair5"),
    new Hair(Loki.Properties.Resources.Short_2, "Hair8"),
    new Hair(Loki.Properties.Resources.Braided_4, "Hair13"),
    new Hair(Loki.Properties.Resources.Side_Swept_1, "Hair9"),
    new Hair(Loki.Properties.Resources.Side_Swept_2, "Hair10"),
    new Hair(Loki.Properties.Resources.Side_Swept_3, "Hair14"),
    new Hair(Loki.Properties.Resources.Pulled_back_curls, "Hair15"),
    new Hair(Loki.Properties.Resources.Gathered_braids, "Hair16"),
    new Hair(Loki.Properties.Resources.Neat_braids, "Hair17"),
    new Hair(Loki.Properties.Resources.Royal_braids, "Hair18"),
    new Hair(Loki.Properties.Resources.Curls_1, "Hair19"),
    new Hair(Loki.Properties.Resources.Curls_2, "Hair20"),
    new Hair(Loki.Properties.Resources.Twin_buns, "Hair21"),
    new Hair(Loki.Properties.Resources.Single_bun, "Hair22"),
    new Hair(Loki.Properties.Resources.Short_curls, "Hair23"),

    new Hair(Loki.Properties.Resources.Blob_hair, "TrophyBlob"),  // ???? Originally in Loki
};

Note: A lot of hairs in Loki's source code was incorrect. Example is a hair being listed as Long 1 when it is actually Ponytail 1. Not sure if that's because the game changed it or not. But the above list is the correct list for both hairs and beards.

For items, the new categories are (ItemType used in InventoryListItem.cs):

Fish = 21,
TwoHandedWeaponLeft = 22,
AmmoNonEquipable = 23,

Example: Dead Raiser -> TwoHandedWeaponLeft Black Metal Misile -> AmmoNonEquipable Wooden Metal Misile -> AmmoNonEquipable Fish -> Perch, Pike, Tuna + Others

With those changes, I'm fairly certain Loki should be flawless and it should technically work fine and support all of the Mistlands updates.

EDIT: The list is still not flawless. There are items like: DvergerArbalest_shoot which doesn't make sense and I haven't figured out how to filter it yet. There doesn't seem to be any parameter differences: https://www.diffchecker.com/IAuh1hkt between two items of the same type (one being an NPC item and one being a Player item).

EDIT: Verified there is no other way to determine if an item is legit.

Brandon-T avatar Dec 22 '22 22:12 Brandon-T

Note: A lot of hairs in Loki's source code was incorrect.

Yes. They were hard coded to be localized and must have been updated since. I changed that and read the beards and hair from the prefab file instead. Which means we'd like to keep those and not filtering them out I suppose. I considered to do like you did above, but opted to skip localization in favor for future updates to be easy:

        private static readonly IEnumerable<Hair> SensibleHairs =
            ItemDb.AllItems
                .Where(i =>
                    i.ItemType == ItemType.Customization
                    && i.ItemName.ToLower().Contains("hair"))
                .Select(i =>
                    new Hair(i.DisplayName, i.ItemName))

and

        private static readonly IEnumerable<Beard> SensibleBeards = 
            ItemDb.AllItems
                .Where(i => 
                    i.ItemType == ItemType.Customization 
                    && i.ItemName.ToLower().Contains("beard"))
                .Select(i => 
                    new Beard(i.DisplayName, i.ItemName));

Also needed is to map new categories (Fish etc) to UI strings. Done that too in my update. Maybe one should deal with customisations like you showed instead of reading them from prefab, to keep possibility to localise. Dunno.

BTW: I changed your program (but haven't succeded in compiling it yet) to produce CSV format as I use that in my PR (see https://github.com/Wufflez/Loki/pull/34 ). My updates for Mistlands can be seen here: https://github.com/jensbrak/Loki/tree/Features/MistlandUpdate (based on PR 34). This is how I intended to use it:

		std::stringstream ss;
		ss << item_data.item_name << ",";
		ss << (item_data.is_teleportable ? "true" : "false") << ",";
		ss << (item_data.uses_durability ? "true" : "false") << ",";
		ss << item_data.max_durability << ",";
		ss << item_data.durability_per_level << "," 
		ss << item_data.max_stack << "," 
		ss << item_data.display_name << ",";
		ss << item_data.max_quality << "," 
		ss << to_string(item_data.item_type) << "\n";		

In essence, instead of storing prefab items hard coded like this:

    public static class ItemDb
    {
        private static readonly Dictionary<string, SharedItemData> ItemData = new Dictionary<string, SharedItemData>
        {
            ["Abomination_attack1"] = new SharedItemData
            {
                ItemName = "Abomination_attack1", IsTeleportable = true, UsesDurability = false, MaxDurability = 100,
                DurabilityPerLevel = 50, MaxStack = 1, DisplayName = "Swing attack",
                MaxQuality = 1, ItemType = (ItemType)3,
            },
            ["Abomination_attack2"] = new SharedItemData
            {
                ItemName = "Abomination_attack2", IsTeleportable = true, UsesDurability = false, MaxDurability = 100,
                DurabilityPerLevel = 50, MaxStack = 1, DisplayName = "Slam attack",
                MaxQuality = 1, ItemType = (ItemType)3,
            }, // ....

I opt to read it from external file (CSV) like this:

public static class ItemDb
    {
        private static readonly Dictionary<string, SharedItemData> ItemData = ReadItemDataFromCsvFile("SharedItemData.csv");

...and...

        private static Dictionary<string, SharedItemData> ReadItemDataFromCsvFile(string fileName)
        {
            try
            {
                using var reader = new StreamReader(fileName);
                using var csv = new CsvHelper.CsvReader(reader, CultureInfo.InvariantCulture);
                var items = csv.GetRecords<SharedItemData>().ToDictionary(item => item.ItemName);
                Debug.WriteLine($"Loaded {items.Count} items to shared item data");
                return items;
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"Failed to load shared item data. Details: {ex.Message}");
                return new Dictionary<string, SharedItemData>();
            }
        }

jensbrak avatar Dec 23 '22 08:12 jensbrak

@jensbrak can you make a compiled release on your Fork ? Thank you

pythaeusone avatar Jan 03 '23 16:01 pythaeusone

can you make a compiled release on your Fork ?

Which branch? The one fixing the crash, the one separating item DB from code as discussed above - or the one adding actual Mistlands support (WIP)? I have more branches (.NET7 for instance) but these are not directly related to this thread and/or Mistlands.

(Either of the branches would be easy to release. A mix of them possible but then I need some time to choose what to merge and how. For instance, it would be logical to make a version that fix the bug (in case items are missing in the future) plus the separation of items data plus mistlands update plus .NET 7. That would, all in all, move Loki to a (imho) much better shape for future updates of Valheim, as well as make it work with Mistlands. )

jensbrak avatar Jan 03 '23 17:01 jensbrak

Had understood that you have already taken the Mistlands items, my mistake :-)

pythaeusone avatar Jan 03 '23 22:01 pythaeusone

@pythaeusone I have, I was just not sure if that's what you wanted. WiP but it works fine so far with Mistlands. I fix a release for you asap!

jensbrak avatar Jan 04 '23 08:01 jensbrak

an you make a compiled release on your Fork ?

Done. Please read disclaimer carefully. I have no intention to run a separate fork in parallell with this official repo. Just a courtesy release for anyone wanting to try out the Mistlands update I made as PR here - before it is merged (IF it is merged!).

jensbrak avatar Jan 04 '23 16:01 jensbrak

Thank you, ill try :-)

pythaeusone avatar Jan 06 '23 22:01 pythaeusone