Daggerfall Mechanics thread: Volunteers welcome

Discuss Daggerfall Unity and Daggerfall Tools for Unity.
Post Reply
ifkopifko
Posts: 195
Joined: Thu Apr 02, 2015 9:03 am

Re: Daggerfall Mechanics thread: Volunteers welcome

Post by ifkopifko »

You might be perfectly right. I assumed that since skill_level is initially derived from Willpower, it would get affected if something changed that attribute.

Anyway, the magicka cost formula can indeed be simplified and partially stored.

Now this is probably "a bit" ahead of time, but the precalculated stored values should be recalculated on load/start/whatever, because surely future mods will modify/balance spells and such.

EDIT: Forget about Comprehend Languages spell, I have stumbled over the solution (or rather my mistakes)... by mere accident ofc. :-D Will post an updated excel file later.

EDIT2: And here is the corrected file...
Attachments
Spells edited 6.zip
(127.22 KiB) Downloaded 212 times

User avatar
Jay_H
Posts: 4061
Joined: Tue Aug 25, 2015 1:54 am
Contact:

Re: Daggerfall Mechanics thread: Volunteers welcome

Post by Jay_H »

ifkopifko wrote:Now this is probably "a bit" ahead of time, but the precalculated stored values should be recalculated on load/start/whatever, because surely future mods will modify/balance spells and such.
An excellent point. I'm thinking too much of the past, not enough of the future. I'm sure it would save time and be entirely compatible with vanilla to recalculate magicka values far more often. Even I have thought of adding a line of "Fortify [skill]" spells, and that would require a different scheme of refreshing values.

I'm looking at the spreadsheet now, and I admit that the math is beyond my understanding, but it looks like ROUNDDOWN(($L$2*$H$2+TRUNC($L$2*$M$2*A8)+TRUNC($L$2*$N$2*TRUNC(B8/C8)))/100,0) is the formula you've put to work, and that it offers the correct value in each case. As far as I can tell, it looks like magicka cost is done now. Would that be right?

ifkopifko
Posts: 195
Joined: Thu Apr 02, 2015 9:03 am

Re: Daggerfall Mechanics thread: Volunteers welcome

Post by ifkopifko »

Yes, that's correct. We now have the formula to provide us with exactly the gold cost and magicka cost of vanilla DF. At least as far as the input data we have go.
I have experimented with simplifying the formula for partial storing (columns on the right called "Simpified input" and "result", where column "R" can be precalculated and stored).

But I would still like to ask you to investigate some magnitude type of spell more closely.

And another thing. When creating a spell in spellmaker, can you set for example "duration" and "magnitude" for one spell at the same time? I'm asking whether to consider adding up of the gold cost, and or magicka cost. Or can you pick only one thing for one spell?

User avatar
Jay_H
Posts: 4061
Joined: Tue Aug 25, 2015 1:54 am
Contact:

Re: Daggerfall Mechanics thread: Volunteers welcome

Post by Jay_H »

ifkopifko wrote:But I would still like to ask you to investigate some magnitude type of spell more closely.
Right, I'm getting ahead of myself. I hope I'll be able to try it within the next couple of days.
ifkopifko wrote:And another thing. When creating a spell in spellmaker, can you set for example "duration" and "magnitude" for one spell at the same time? I'm asking whether to consider adding up of the gold cost, and or magicka cost. Or can you pick only one thing for one spell?
Yes, each modifier can be changed independently and concurrently of the others. A Regenerate spell can be created with a Duration formula of (5+(level*3)/2) and a Magnitude of (4+(level*1)/3), just as an example. Daggerfall only adds each of the values, and doesn't use multiplication or anything more complicated to combine them.

To show this, we can say the Duration value for a spell would cost 160 gold and 28 magicka, and the Magnitude would cost 220 gold and 42 magicka. The spell would cost 380 gold and 70 magicka, through regular addition.

User avatar
Jay_H
Posts: 4061
Joined: Tue Aug 25, 2015 1:54 am
Contact:

Re: Daggerfall Mechanics thread: Volunteers welcome

Post by Jay_H »

I haven't had time yet and it looks like I won't have time soon, so I made a quick pass through some of the values. I made new sheets for Heal Health Range 50 and Drain Range 50. It's hard to put the results into words, but the values only rise on every other increase.
Attachments
Spells edited 7.xls.zip
(94.36 KiB) Downloaded 205 times

ifkopifko
Posts: 195
Joined: Thu Apr 02, 2015 9:03 am

Re: Daggerfall Mechanics thread: Volunteers welcome

Post by ifkopifko »

Thanks.
As expected, it uses truncated average of the lower and upper bounds. :) BTW: I assumed, that the missing column "/Levels" was supposed to equal "1", so I added it to the sheet. Is that correct?

So the work on spells seems to be comple now... a big thanks to Al-Khwarizmi for the starting impulse. :)

And now the more complicated issues... like combat for example. I suppose it's going to get a lot more messy there. Good input data (good testing methodology) will be key.
Attachments
Spells edited 8.zip
(172.09 KiB) Downloaded 200 times

Narf the Mouse
Posts: 833
Joined: Mon Nov 30, 2015 6:32 pm

Re: Daggerfall Mechanics thread: Volunteers welcome

Post by Narf the Mouse »

Jay_H wrote:
ifkopifko wrote:
Narf the Mouse wrote:I could do a post on optimization, if anyone wants; it's a hobby of mine that I enjoy. ;)
I'm always curious about technology and I wouldn't mind learning more. If you write it at a layperson's level I'll read it.
So, basic rules of optimization, then some explanations. I tried to keep this is simple as I could possible, but you might not want to read it in one sitting.

1) Do not prematurely optimize.

The basic idea of this rule is to never implement an optimization without first doing speed tests. Various software can tell you where your code is slow; often down to the individual instruction. When testing your code like this, it will run slow. Sometimes veeery slow. This is because the testing software inserts instructions into your code to call into its software, so it can count how long things take, and how many calls there are.

The upside is, when you do go to speed up your code, you will know where your code is slowest. And you will know whether your optimizations are actually faster, and by how much; by running the speed test afterwards. Without running tests, you have to rely on eyeballing it to see if the code is faster; and you don't necessarily know how, or why, or necessarily if it really is; or by how much.

If you can't afford speed test software, an alternative is to use the System.Diagnostics.Stopwatch class. If you use a #define to exclude them if, say, "SpeedTesting" is not defined, they'll also be easier to mange. This page describes how to use and implement Unity Game Engine defines: https://docs.unity3d.com/Manual/Platfor ... ation.html

Note that this is somewhat advanced, and would really be the topic of an additional post.

2) The 80/20 rule.

The rule: 80% of your slowdown comes from 20% of your code.

Now, to explain why this is. There are a few basic types of slowdown that occur most often for this purpose.

2.1) Code that is called a lot, but is tightly optimized already.

This is a good candidate for caching. You've already done your job here; the code is fast and efficient. The only thing left to do is to see if you can call it less, and if caching is indeed faster (remind me to explain why it might not be in a later post). So, here you don't optimize the code anymore; you optimize the callers.

Alternately, you may be pushing a big chunk of data to and/or from the method, which is a good candidate for using an out variable. Out variables are passed by reference, which means that, instead of passing a large struct (for example), the location of the struct in memory gets passed. This might not actually be faster, because accessing a location in memory that's being pointed to, rather than directly accessing the data, can be slow itself. As always, test. :)
This will also change how the method is called, which means it can break external code. Alternately, add a method that returns data using an out variable, and redirect your existing method here. This will allow callers to move to your method over time, and when they chose to (rather than breaking their code).

Code: Select all

MyData MethodCall()
{
	// Operations.
}
to

Code: Select all

MyData MethodCall()
{
	MyData output;
	MethodCall(out output);
	return output;
}
void MethodCall(out output)
{
	// Operations.
}
2.1) Code that is called occasionally, but is very large, complicated, and/or calls into external services.

Caching can be useful here, too, if possible. But first, optimize. :) Unexpected lag in FPS is often worse than slow FPS. Ok, one at a time.

2.1.1) Code that is very large.

Try to trim it down; are you repeating yourself anywhere? Do you need all this code in one method, or is it really doing different things depending on the inputs? In short, can you break it into smaller methods that can be called individually? And are you repeating any calcs?

Next, is all of the code necessary? Can you apply any optimizations to it?

2.1.2) Code that is complicated.

This is pretty much handled like the previous case, and is generally a good candidate for breaking up into discrete methods. In addition, after code is put into smaller methods, you often see ways that that smaller code can be optimized, because your eyes are not being hit with a ton of complex code.

2.1.3) Code that calls into external services.

Caching is good here, too; as is replacing it with a faster service. But the one thing you should nearly always do, if at all possible, is call it asynchronously. Not if it's fast already; but if it were fast already, you wouldn't be here. :)

Ok, so what's "calling asynchronously"? If the external service you are calling has a method with the same name with "async" added (or a similar name), you call that method, pass in a method to call when it is done, and go back to your own business. When the external service is done, it will call your method. What you should do, at this point, is dump the data in variables that you only read, and your callback method you pass to the service, only writes to.

Otherwise, you can have a situation analogous to multiple people trying to work on the same part of a project, with the same tools, at the same time. Problems arise. ;)

An example of how you'd write this code:

Code: Select all

// myDataLockObject must only be used specifically in the two places given.
// Otherwise, problems will arise.
private object myDataLockObject = new object();
private MyData dataFromExternal;
private bool myDataChanged;
private MyData data;
private ExternalService slowExternalService;

void Callback(MyData data)
{
	// This ensures that no-one reads the data while its being written
	// if other code tries to lock myDataLockObject while data is being
	// written to, it will wait until we exit this lock.
	// Only one piece of code at a time can lock a class.
	// Structs can't be locked; the explanation is a post itself. :)
	lock (myDataLockObject)
	{
		// Only write.
		this.dataFromExternal = data;
		myDataChanged = true;
	}
	// Locking is slow, but generally much, much faster than waiting
	// for an external service that's been determined to be a member
	// of the 20%. :)
}

void Update()
{
	if (myDataChanged)
		UpdateMyData();
	
	// ...Do stuff with data. At some point, use the method
	// CallSlowExternalService().
}

void CallSlowExternalService()
{
	// Call external service.
	slowExternalService.GetDataAsync(// ...input data, callback: Callback);
}

void UpdateMyData()
{
	// It may seem strange to call it like this, but it ensures that
	// no other code mistakenly calls this, and that some other code
	// hasn't resolved this while we waited for the lock to open.
	if (myDataChanged)
	{
		lock (myDataLockObject)
		{
			// After all, we can't guarantee our future code won't
			// call this elsewhere, perhaps from a different thread.
			// Threads are a topic of their own.
			// This triple-gate structure provides safety, at the code
			// of a little speed.
			if (myDataChanged)
			{
				data = dataFromExternal.
				myDataChanged = false;
			}
		}
	}
}
The only problem here is if the data needs to be current and up-to-date, in which case your options tend to be to either remove that requirement somehow, use a faster service, or grit your teeth and bear it.

2.2) Code that uses expensive statements.

Which statements are expensive can't always be told without testing, but some things to look at dubiously:

2.2.1) Lots of method calls. But not necessarily for the reason you think; speed profiling software is often terrible (in my experience) at determining the slowdown of method calls themselves, because they have a tendency to cause the C# runtime engine to not inline said methods. Inlining a method is when you put the body (code) of a method where the method was. So, for example, a trivial call like "Vector3 GetPosition { get { return position; } }" will often be replaced with a direct lookup of position in the release code. Inlining is also a topic for a whole other post, so I'll simply note some general things to look askance at.

Method calls to large methods. Your methods should each do one thing, and if its calling large methods, it might be doing more than one thing. Methods should only do one thing, because otherwise you get side-effects. Granted, that one thing might be "run the game", but it isn't also "change your default browser", for a silly example. :)

2.2.2) Lots of if statements. If statements involve goto's internally, and goto's are slower. Try to consolidate them into fewer if statements. It might not make a huge difference, but it might also; and it probably won't make no or negative difference. Unless the compiler's optimizer already handled that. In which case, revert, move on to another part of the method.

2.2.3) Using the wrong collection.

If you're removing and inserting data from arbitrary locations in the collection, then a List is generally a bad idea. Nearly each time you do so, List has to recreate its internal array of data. Yeah. I say nearly, because there's one case where you can remove data from arbitary locations, quickly. And that's if you don't care about the order of the data in the list, you can do a replace-remove.

Code: Select all

listOfData[indexOfItemToRemove] = listOfData[listofData.Count - 1];
listOfData.RemoveAt(listOfData.Count - 1);
I can't guarantee this will be faster in Unity C#; I haven't had an opportunity to test it, at least recently. However, in .Net C#, if you remove the last element in a List, it simply reduces the .Count by 1 and sets that array index to default(T). Which is null for classes.

We're gonna get complex here; hopefully its clear enough. This is a complex topic.

A better idea is often to use a HashSet or Dictionary. Ok, bit of explanation of Big O notation. It's a quick way of saying how complex an operation is in the worse case (and thus, generally, how slow in the worse case). O(1) means the operation does one thing, then exits. O(n) means the operation involves every single item in the collection (note that these are worse cases). For example, finding a specific item is O(n) in a list...And O(1) in a HashSet or Dictionary.
O (n^2) means that the operation involves every single item in your array...Interacting with all the other elements of your array. Certain types of sorting can involve this. Fortunately, the default C# List.Sort() is generally much faster. Another common one is O(n * Log(n)), or in short, if you have 16 items, you have (16 * Log(16)) = (16 * 4) operations. Priority queues usually use this. Unfortunately, C# does not include a default priority queue. And they're a whole other post, too. :)

Note that a Big O of O(n) can still be faster than O(1)...If that one operation is complex, and the number of items is very small.

For further reading, see the internet.

2.2.4) Using an inefficient algorithm.

Algorithms tend to be a major part of the 20%. Unfortunately, I can't cover much here, because Algorithms comprise entire College courses. However, Big O notation is helpful here; try to figure out the Big O for your algorithm, and look for a collection or method that will reduce the complex parts of the algorithm, and lower the O notation. Also, see if you can apply other methods or ideas listed here or elsewhere.

This post looks long already, so I'll cut it off here.
Previous experience tells me it's very easy to misunderstand the tone, intent, or meaning of what I've posted. If you have questions, ask.

User avatar
Jay_H
Posts: 4061
Joined: Tue Aug 25, 2015 1:54 am
Contact:

Re: Daggerfall Mechanics thread: Volunteers welcome

Post by Jay_H »

ifkopifko wrote:Thanks.
I assumed, that the missing column "/Levels" was supposed to equal "1", so I added it to the sheet. Is that correct?
Yes, I hadn't put that in there. Levels was 1 in every case.
ifkopifko wrote:So the work on spells seems to be comple now... a big thanks to Al-Khwarizmi for the starting impulse. :)
With my original testing method, I wouldn't have finished until August. You guys are fantastic.

Narf the Mouse, I've begun reading, but your recommendation to not read it all at once is quite wise :) I think there are some questions I'll have by the end. I'll see if they aren't resolved along the way.

Narf the Mouse
Posts: 833
Joined: Mon Nov 30, 2015 6:32 pm

Re: Daggerfall Mechanics thread: Volunteers welcome

Post by Narf the Mouse »

Jay_H wrote:Narf the Mouse, I've begun reading, but your recommendation to not read it all at once is quite wise :) I think there are some questions I'll have by the end. I'll see if they aren't resolved along the way.
It took me years to learn some of this stuff. If I can teach it to you (and maybe others) in a few days, it's easily time well spent. :)

Learning from your own mistakes is all well and good; but a better life optimization is learning from the mistakes of others. :)
Previous experience tells me it's very easy to misunderstand the tone, intent, or meaning of what I've posted. If you have questions, ask.

User avatar
Jay_H
Posts: 4061
Joined: Tue Aug 25, 2015 1:54 am
Contact:

Re: Daggerfall Mechanics thread: Volunteers welcome

Post by Jay_H »

I've come back to testing To-Hit. I'm using a normal stats character as a baseline now so we can get a better perspective. My level is 15, regular stats are all 50, all weapon skills at 50, swinging right to left, Daedric Mace. Unfortunately, my first test isn't encouraging.

Baseline:

15/15
14/15
14/14

97.72%. Not what I was expecting.

Level 1:

15/16
16/18
15/15

93.88%.


Blunt Weapon 1:

15/21
15/21
16/22

71.88%. Now I have a real baseline to work with. Every following test should assume Level 1, Blunt Weapon 1, all other skills and stats at 50 and no advantages or disadvantages unless stated.

Agility 1

15/19
15/22
13/16

75.44%. I'm expecting Agility to provide a bonus to defense only, not offense.

Agility 100

15/19
16/24
13/17

73.33%.

Long Blade 1, Daedric Saber

13/18
12/28
15/17
16/26
16/20

That's 74.07% if we discount that statistical aberration in test 2, and 66.06% if we don't.

Long Blade 1, Agility 1, Daedric Saber

14/21
14/19
14/23

66.66%.

Long Blade 1, Agility 100, Daedric Saber

14/19
14/17
15/18
14/20
14/24

72.45%, or 77.03% if we discount the last one. This is still fairly inconclusive, but I have two theories: one is that Agility does absolutely nothing even in Long Blade (and definitely in Blunt Weapon). The other is that the Agility Modifier ranging from -5 to +5 is just a flat percent modifier: a roll at 65% would become 70% for a character with 100 agility, for example. That means Agility would be trivial at best.

Long Blade 1, Strength 1, Daedric Saber

30/50
29/50

59.00%. Strength certainly affects To-Hit for Long Blade.

Long Blade 1, Strength 100, Daedric Saber

9/10
8/12
9/18
9/14
8/8
9/13

69.33% if we count all of them, and 75.44% if we discount the third test. An underwhelming change. I still feel like I'm far from the answer.

Long Blade 100, Strength 1, Daedric Saber

33/35
41/50

87.06%.

Long Blade 100, Daedric Saber

14/14
14/14
15/15
14/14
14/14

100.00%. Either this is a big anomaly or perfect accuracy is possible.

Strength 100, Long Blade 100, Daedric Saber

8/8
8/8
8/9
8/8
9/9

97.62%.

Agility 1, Long Blade 100, Daedric Saber

13/13
14/14
14/14

100.00%.

Strength 1:

34/63

53.97%.

Strength 100:

9/13
9/17
9/17
9/14
9/12

61.64%. Definitely some statistical anomalies here, since my testing last week provided much higher results, and Strength 100 here gives lower results than Strength 50.

Blunt Weapon 100:

14/14
16/17
15/15

97.83%.

Strength 1, Blunt Weapon 100:

37/48

77.08%.

Strength 100, Blunt Weapon 100:

9/10
9/9
8/9
9/9
9/9

95.65%.

Agility 1, Blunt Weapon 100:

14/14
14/14
15/15

100.00%.

The results are interesting, though I don't want to rely on them too much yet. It's clear there are some serious numerical deficiencies this time around. More testing will bring more precise results.
Attachments
Combatsave.zip
(39.48 KiB) Downloaded 191 times
To-Hit.xlsx.zip
(4.51 KiB) Downloaded 196 times

Post Reply