-
Content Count
243 -
Joined
-
Last visited
-
Days Won
11
Content Type
Profiles
Forums
Blogs
Forum & Tracker Requests
Downloads
Gallery
Store
Calendar
Everything posted by AmanoJyaku
-
What version are you running? I just installed v1.0.2210.16738 on Win10 64-bit without installing .Net or setting a compatibility mode. I get .Net Framework errors at startup, but it runs if I click "Continue".
-
Nearly finished Nar Shaddaa
Got good loot
Realized I forgot to install M4-78
... FML
-
I'm looking forward to the redux of this status update which will be something along the lines of "Got five minutes into M4-78. Realised the magnitude of my error. Formatting my HDD and throwing the computer out the window. FML.".
-
lol I beat the game when it came out in 2004. Haven't yet beat it with RCM or M4-78, so I want to try them both.
Anyway, I backed up the saves. I can always go back to them, although starting the game with three Trandoshan swords, Krath holy armor, and Jal Shey Advisor Armor feels like cheating. The random loot generator was so good to me.
-
-
Just rewatched the prequel trilogy. It was worse than I remembered. George Lucas has no talent as a writer.
Star Wars must be an accident.
-
Quote
The Ewoks were originally going to be Wookies
Yeah, that's why they included that Kashyyyk fight in ROTS, a recycling of the original idea.
As to writing, remember that the OT scripts were heavily edited by other authors, mostly uncredited. For example. Again, the PT more clearly shows what Lucas was capable of when left without adult supervision.
- Show next comments 9 more
-
-
Sometimes, I learn more from mistakes than I do from getting it right the first time... My last post mentioned bug fixes. In an earlier post I described the structure of an NCS subroutine: Step 1 - The code that does the stuff you want Step 2 - Placing a return value on the stack Step 3 - Destroying local variables and temporaries on the stack Step 4 - Destroying arguments on the stack None of these steps are required, I'm almost certain I've seen an empty subroutine that simply returned. That means a missing step cannot impede the analysis of successive steps. So, how do we analyze the following? Subroutine 1601 1601 CONSTI 0 1607 CPTOPSP -3 1 1615 CPTOPSP -3 1 1623 JSR 298 1629 MOVSP -2 1635 RETN There is a single path through the code, that is there are no jumps so the ops execute in sequence. (Technically, the JSR is a jump. However, subroutine calls return to the op from which they were called and proceed to the next op in the sequence.) So, how does the stack change? The first three ops each add a value to the stack. The fourth op, a subroutine call, removes those three values from the stack. The subroutine returns void, i.e. nothing, so no return value is placed on the stack. And yet, the fifth op removes values from the empty stack... This tells us a few things: The fourth op is the last in Step 1 Subroutine 1601 does not place a value onto the stack after Step 1, so there is no Step 2 There are no local variables and no temporary values on the stack, so there is no Step 3 Therefore, the fifth op must be Step 4 Subroutine 1601 takes two arguments and returns void, i.e. void Sub1601(Arg0, Arg1) Let's look at a more complicated example: Subroutine 87299 87299 CPTOPSP -1 1 87307 CPTOPBP -171 1 87315 EQUALxx 87317 JZ 89610 CONSTx "NO COMBO SELECTED" 89631 CPDOWNSP -3 1 89639 MOVSP -1 89657 MOVSP -1 The first two ops each place a value onto the stack. The third removes those two values, and replaces them with a boolean (actually, an integer) result. The fourth removes the result, then performs a conditional jump. To simplify things, I've removed the conditional code branches. At this point, the stack is empty. The fifth op places a string onto the stack. The sixth copies the string down three positions from the top of the stack. However, there is only one value on the stack so this may be a return... The seventh op clears the stack. The eighth tries to remove a value from the empty stack. So, what does this tell us? The fourth op is the last in Part 1 Ops five and six may be Part 2 The seventh op is Part 3 The eighth and final op is Part 4 You'll notice the repeated use of "may". We should not assume all writes outside of the subroutine's stack are returns. Subroutine arguments also exist outside of the subroutine's stack, and this turned out to be the source of the bug. One of the subroutines analyzed wrote to its argument, and this was mistakenly marked as a return value. Thus, a subroutine that returned void, i.e. nothing, but also modified an argument messed up the analysis of any other subroutine that called it. The fix was simple enough: record the last write outside of the stack, along with the difference between the negation of destination and size of the stack. In the example above, the destination is -3 and the stack size is 1: -(-3) - 1 yields 2. When analyzing Part 4, the destruction of subroutine arguments, we compare the result to the size of the negation of number of arguments being destroyed: 2 vs -(-1), or 2 > 1. This tells us the final write is to a position greater than the furthest argument, thus it must be a return value. Subroutine 87299 takes one argument and returns one value. In short, we have to analyze Part 4 before we can analyze Part 2. Fixing bugs wasn't the only benefit. It's removed from the example, but there is an unconditional jump after the first MOVSP that transfers execution to the second MOVSP. This jump bypasses an op, creating dead code. I seem to have finally discovered the source of dead code: return statements. This also solves a different problem: I now know how to identify return statements in different parts of the subroutine. More in a future update!
-
That depends on what you consider reasonable. Your "reasonable" might include murdering everyone because it's fun... You need to persuade them to be peaceful, and you can't do anything that will upset them. Persuading takes different stats (I think the Serroco back down if you have a Strength attribute of 16 or more). And you're not supposed to open treasure in Saquesh's territory as that's considering stealing from the Exchange (I forget what happens if you open treasure in front of the Serroco). To put it simply, your only option may be to fight if you don't have high stats and you want all the loot. Wiping out both groups has no negative impact on the game, and if you're trying meet Goto by angering the Exchange then you benefit from killing Saquesh and his men.
-
Just got my second vaccine. DarthParametric, you can take your mask off. I'm not contagious anymore!!!
-
Sorry for the delay. I wanted to put out an update, but I've been addressing bugs. More important, I'm dealing with real life and needed to take some time for myself. This project has consumed most of my free time, I haven't played a game since February of last year. I'll pick this up next week, but for now I'm playing some games!
-
Just a quick update. Here's output from TSL's k_contain_unlock.ncs: Overall, the results look good! Though, there are things that need to be dealt with: Subroutines only list the number of return values and arguments; type information needs to be substituted, e.g. void Sub1122(object, int, int) Arguments in expressions are listed as CPTOPSP; this needs to be replaced with a variable name and argument position, e.g. if (!GetLocalBoolean(Arg0, 57)) Variable declarators with initializers are written as two lines; while correct, these should be merged, e.g. object Object0 = 0; Arguments that are assigned to need to be printed, e.g. if (Arg2 < 900) Arg2 = 0; For loops are presented as while loops; while correct, for (Int0 = 1 ; Int0 <= Arg1; ++Int0) is more compact than Int0 = 0; while (Int0 <= Arg1) ++Int0 Increment and decrement are not yet printed (this is trivial, I just didn't do it) Printing expressions needs to take place at a different point in the process; the current method prints some expressions twice, and omits others Switch statements need to be printed Vectors and structs need to be detected, which will require multiple methods Return statements need to be printed Break and continue statements in loops need to be detected Arguments of type "action" (AssignCommand, DelayCommand, ActionDoCommand) need to be printed These are largely minor issues, but they're taking a backseat to a bug I introduced along the way: The else-if statements have the wrong test condition. They should all be pointing at the same variable as the preceding if statement, Arg1. This error is propagated further as reflected in the last line. Removing 5 arguments? There are only 2!!! Printing code was added yesterday, so I expect this to be a quick fix.
-
Here you go: d3_saber_force.ncs
-
I wanted to give an update a few weeks ago, but I had to tackle a problem I ignored for months. The reverse compiler is now more than 5,000 lines in length, and that makes it difficult to maintain. So, almost 4,000 lines of source code have been moved into a library. Additionally, many functions did too much and have since been split into smaller functions or replaced entirely. It's not sexy, but coding never is. Or is it? Cue: Salt-N-Peppa's "Let's talk about sex" Let's talk about Hex, baby! Let's talk about Binary! Let's talk about Octal numbers, Signed and unsigned, number theory! OK, OK, I'm back to "normal". Anyway, after three weeks of maintenance I got back to the real work and decided to rewrite the remaining 1,000+ lines entirely. Everything from reading an NCS file from disk to producing NWScript is new. And so much better than before, since I eliminated some bugs in the code. I'll provide a more detailed post later, but here's some sample output from TSL's k_inc_npckill.ncs: I purposely left out nested scopes because I wanted to focus local variables. This script is particularly interesting because it contains a vector. See the really long line that starts with float = GetPositionFromLocation? That's one of the vector's members. The source code looks like this: The really long line in the sample output essentially merges the two lines above, but if you look closely you'll see it's the combination of creating vPos.z, adding 1.0f to vPos.z, then storing the result back to vPos.z. Here's a cut-and-paste from the NSS file to compare to the sample output: Now that I've confirmed the stack is being created correctly, the next tasks are to: Add variable declarations, e.g. int nParam, object oParam Properly display modification of variable values, e.g. vPos.z = vPoz.z + 1.0f Insert nested scopes (if-else, while, etc...) Probably other stuff I can't think of right now
-
Stripped out some of the diagnostic output and added some source code output. Here's the original Handmaiden conditional script again: //c_003handfight int StartingConditional() { string tString = GetScriptStringParameter(); int tInt = GetScriptParameter( 1 ); int LevelInt = GetScriptParameter( 2 ); if ( ( GetGlobalNumber(tString) == tInt ) && ( GetGlobalNumber ("G_PC_LEVEL") < LevelInt ) ) { return TRUE; } return FALSE; } Here's the output from the reverse compiler: //Kotor2\c_003handfight.ncs This is definitely a K2 file Sub13, 23 } Sub23, 250 38 string = GetScriptStringParameter(); 65 int = GetScriptParameter(1, ); 92 int = GetScriptParameter(2, ); 166 if (GetGlobalNumber(CPTOPSP, ) == CPTOPSP && GetGlobalNumber("G_PC_LEVEL", ) < CPTOPSP) { } 224 ; } Recognizable! Here's the output of k_inc_npckill.ncs: And k_contain_unlock: Rough, but getting there!
-
I fixed the switch bug and decided to try the first step of reverse compiling: finding statements. For example, the following has six statements: //c_003handfight int StartingConditional() { string tString = GetScriptStringParameter(); int tInt = GetScriptParameter( 1 ); int LevelInt = GetScriptParameter( 2 ); if ( ( GetGlobalNumber(tString) == tInt ) && ( GetGlobalNumber ("G_PC_LEVEL") < LevelInt ) ) { return TRUE; } return FALSE; } The first three are declaration statements, they introduce entities into the StartingConditional subroutine scope. In C, variables and subroutines (functions) are entities. Entities have names and types. In an NCS file, subroutines are found by the JSR opcode and variables are created by the RSADDx opcode. The return statement is one of the jump statements, which includes the break and continue statements. All three are implemented by the JMP opcode. The if statement is one of the selection statements, which includes the switch statement. The if statement is also a compound statement or block, meaning it can contain multiple statements. The previous updates detailed my efforts to correctly identify the various types of statements. Here's what the reverse compiler produces: This is definitely a K2 file Sub13, 23 13 adds 1 15 Sub23 removes 0 returns 1 21 Sub23, 250 23 adds 1 25 GetScriptStringParameter adds 1 30 38 removes -1 44 adds 1 46 adds 1 52 GetScriptParameter removes -1 adds 1 57 65 removes -1 71 adds 1 73 adds 1 79 GetScriptParameter removes -1 adds 1 84 92 removes -1 98 adds 1 106 GetGlobalNumber removes -1 adds 1 111 adds 1 119 removes -1 121 adds 1 129 removes -1 135 adds 1 149 GetGlobalNumber removes -1 adds 1 154 adds 1 162 removes -1 164 removes -1 166 if() removes -1 210 adds 1 216 224 removes -4 230 248 Sub13 is the hidden _start() function. This file is a conditional script used in dialogs, so it returns a value. This is the file you use to practice fighting with Handmaiden, and she won't fight you if you aren't good enough. The value returned is obtained from Sub23 and stored in the space reserved by the RSADDx opcode at offset 13 (written above as "add 1"). Sub23 is int StartingConditional(), which is what is required for conditional scripts. The alternative is void main() for non-dialog scripts. If this was a non-dialog script offset 13 would not be a reservation. It would be Sub21, because the JSR opcode is six bytes and the next opcode is the two-byte RETN (13 + 6 + 2 = 21). Offsets 23-38 are the a declaration statement, string tString = GetScriptStringParameter(). You see three elements added to the stack, and two removed. Offset 30 is the CPDOWNSP opcode. Blank lines mean elements have not been added to or removed from the stack. Instead, CPDOWNSP copies the value returned by GetScriptStringParameter() to the element reserved by offset 23, string tString. CPDOWNSP is the assignment operator, "=". Offset 38 then destroys the value returned by GetScriptStringParameter() since it is no longer needed. In C this is known as discarding. The key to writing a reverse compiler is finding these discarded values. The MOVSP opcode discards values, so it's a safe bet that a MOVSP opcode is the end of a statement. In the above example, offsets 38, 65, and 92 are discarding values. You can think of MOVSP as the semicolon at the end of the statement. Offset 164 is the logical AND, 98-162 are the operands. Since we have already found the ranges created by JZ and JNZ, we already know the boundaries of those block statements. Offset 166 is the if () statement, which ends at offset 210. And blocks are surrounded by curly braces. Offset 210 places the return value onto the stack. This is after the if () statement, so it's value is FALSE. It's then returned at offset 216 using CPDOWNSP to the reservation created at offset 13. Offset 224 then destroys the three local variables and the return value using MOVSP. Finally, the RETN opcode should be at offset 230, but for some reason it's a JMP to 248. (I swear, I hate the NCS compilers.) Offset 248 has the RETN. Neither the JMP or RETN would be printed. TL;DR I just have to remove diagnostic info, group expressions into statements, and ensure the branching statements and nested statements are handled correctly. Look forward to another update soon.
-
All he said was he doesn't want to report on anything unless he's 100% sure (previous tweets deleted), and in response to "who should EA pick to make the game" he replied no one would guess right. He isn't confirming, denying, or hinting at anything.
-
The Phantom Bug Attack of the Cases Revenge of the Switch
-
I promised an update in two weeks... How about two days? The approach of using ranges has borne fruit. Consider the following code: int subroutine(int I, float F, vector V) { int i = 1; while(true) { if(i > 10) { //do stuff } else { return -1; } } return 0; } The reverse compiler needs to be able to find the return type and parameter types. To do this, it: Counts the number of local variables in Part 1 Counts the number of variables returned in Part 2 Destroys the local and returned variables in Part 3 Verifies the stack is empty, and assumes the count being destroyed is the number of input arguments in Part 4 Using the above code as an example, the method outlined in the post from two days ago effectively ignores everything inside the while loop. So: 1 local variable is counted, created by the RSADDI opcode 1 return variable is counted, created by the CPTOPSP opcode and copied by the CPDOWNSP opcode 1 local variable and 1 returned variable are destroyed by the MOVSP opcode 5 input arguments are destroyed by the MOVSP opcode (vectors are three floats) Here's the output of the KoTOR2 file k_contain_unlock.ncs: This is definitely a K2 file Subroutine 13, 21 JSR Subroutine 21, 995 JSR Subroutine 995, 1122 Return variables: 0 Local variables: 0 Parameters: 0 Subroutine 1122, 1766 Return variables: 0 Local variables: 0 Parameters: 3 Subroutine 1766, 2948 Return variables: 1 Local variables: 0 Parameters: 2 Subroutine 2948, 6915 JSR Subroutine 6915, 7116 Return variables: 1 Local variables: 0 Parameters: 1 A subroutine with the status "JSR" means it depends upon another subroutine to be reverse compiled first: Sub13 calls Sub21 Sub21 calls Sub995 Sub2948 calls Sub6915 Here's what the other status codes mean: Return variables: 0 - returns void; 1 - returns a single value, e.g. int, float, string or object; a value of 2 or more returns a struct or vector Local variables: 0 - should always be 0, because it's calculated after the stack is cleared; non-zero value means the analysis is flawed Parameters: 0 - there are no input parameters; 1 or more means there are input parameters So, what are the subroutines? Sub13 - void _start() - the hidden routine Sub21 - void _global() - the routine that creates global variables Sub995 - int main() Sub1122 - void PlaceTreasureDisposable(object oContainer = OBJECT_SELF, int numberOfItems = 1, int nItemType = 900) Sub1766 - string GetTreasureBundle (int nItemLevel, int nItemType = 0) Sub2948 - string GetBundlePrefix (int nItemLevel, int nItemType) Sub6915 - string SWTR_GetQuantity(int iCount) Still targeting an update in two weeks. Or earlier.
-
Life has been crazy, and I've seen things I never thought would happen in my country. But, coding never stops. Time for an overdue update! An earlier update outlined the structure of a subroutine: return_type subA(Argument_1, Argument_2...) { //Part 1 - The code that does stuff //Part 2 - Values returned //Part 3 - Destruction of local variables //Part 4 - Destruction of arguments } Part 2 returns values from a subroutine using the CPDOWNSP opcode. Parts 3 and 4 destroy local variables and arguments using the MOVSP opcode. These opcodes can appear in any part of a subroutine. So, how do we know if we're returning a value, and destroying variables and/or arguments? We analyze part 1 to determine what, if anything, is on the stack. Computers execute operations in sequence unless directed otherwise. In high-level languages like NWScript, control statements alter the sequence. An if statement conditionally executes code. If the test condition fails, this code is jumped over. So, what does an if statement look like in the compiled NCS? 102 CPTOPSP -3 1 110 CONSTI 0 116 EQUALxx 118 JZ 170 124 CPTOPSP -1 1 132 CPTOPSP -3 1 140 CONSTO object 146 JSR 298 152 MOVSP -3 158 JMP 296 164 JMP 170 In the example, the opcode at offset 102 of the NCS file copies the value of a variable to the top of the stack. Offset 110 places the value 0 onto the stack. Offset 116 performs a test of equality of the two values, removes them from the stack, then places the result of the test onto the stack. Offset 118 performs a conditional jump based on the result, removing the result in the process. Offsets 124-164 are the body of the if statement, which consists of a call to a subroutine followed by a return statement: if ( nKillMode == 0 ) { DamagingExplosion(OBJECT_SELF, nDelay, nDamage); return; } So many ops, so few statements. That's why we write in high-level languages instead of assembly, because the compiler allows us to focus on what the program should do rather than creating the proper sequence of opcodes to keep the program from breaking. Notice in the body of the if statement there is a MOVSP. That is the destruction of local variables, part 3. There is no part 2, because the subroutine returns void (nothing). But, how do we know the MOVSP is part 3 and not part 4? And why are there two unconditional jumps??? The challenge of writing a reverse compiler is knowing the rules used to compile. I've spent the past six months observing, analyzing, and documenting those rules. For example, all control statements other than the switch statement are created using the jump-if-zero (JZ) opcode. (Switch statements are created using the jump-if-not-zero [JNZ] opcode.) However, the existence of a JZ opcode does not mean you've encountered a control statement! JZ is used to create the following: Logical AND Logical OR if statements else if statements while statements for statements do-while statements (I finally found one!!!) This means we need to some way of determining what the range of addresses created by the JZ opcode is. In the example above, the range created by the if statement is [124, 170). In this range notation a bracket "[" means the offset is included in the range, and a parenthesis ")" means the offset is outside of the range. The 170 is obvious because it's specified by the JZ opcode; if nKillMode == 0 returns false we jump to 170 and resume execution from there. Where does the 124 come from? Remember what was stated above: "computers execute operations in sequence unless directed otherwise." A JZ opcode is 6 bytes in length and the JZ is at offset 118, so 118 + 6 means the next op in the sequence is at offset 124. If nKillMode == 0 returns true we enter the body of the if statement at offset 124. Offset 124 is the first address in the range, offset 170 is the first address outside of the range. All control statements are ranges, but not all ranges are control statements. The NCS file is itself a range: [0, file size). Subroutines are ranges. However, our focus is on the ranges created by JZ. How do we determine what they are? The unconditional jump, JMP. Notice the example ends with two JMPs. This caused me lots of stress because I didn't know why they were there. I'm now convinced this is one of the rules of compiling NCS files: control statements end with a JMP. The if statement is easily identified as such because the second JMP goes to the same offset as the JZ. The true block of an if-else statement ends in a JMP to an offset greater than that of the JZ. The if and else-if statements all end in JMPs to the same offset. Loops all end in JMPs to an offset less than the offset of the JZ. Logical AND and Logical Or are not control statements, so they do not end in a JMP. Our example has two JMPs. Why? Most likely the original compiler was dumb. The NWScript return statement is translated into a JMP to the end of the subroutine, offset 296. The opcode at this offset is RETN, the last opcode in a subroutine. Anytime you see a return in NWScript that isn't the last statement in the subroutine, you know it will be implemented as a JMP: if ( nKillMode == 0 ) { return; } if ( nKillMode == 1 ) { return; } if ( nKillMode == 2 ) { return; } is 118 JZ 170 158 JMP 296 164 JMP 170 186 JZ 230 218 JMP 296 224 JMP 230 246 JZ 290 278 JMP 296 284 JMP 290 296 RETN The original compiler just stuck the body of the if statement in between the JZ and JMP. A modern compiler would have removed the second JMP since it's unreachable code. OK, this is great and all. But what about the reverse compiler? Well, now that I have the rules down I'm close to the finish line. The reverse compiler accurately finds ranges and determines what types they are. The output looks like this: This is definitely a K2 file Subroutine 13, 21 13, 21 //Subroutine Subroutine 21, 298 21, 298 //Subroutine 124, 170 //if 192, 230 //if 252, 290 //if Subroutine 298, 1345 298, 1345 //Subroutine 320, 396 //if 704, 722 //logical AND or OR 728, 1216 //loop 787, 805 //logical AND or OR 811, 1135 //if 914, 970 //if-else 1017, 1043 //if-else Subroutine 1345, 1601 1345, 1601 //Subroutine 1459, 1553 //if 1511, 1547 //if Subroutine 1601, 1637 1601, 1637 //Subroutine Subroutine 1637, 1813 1637, 1813 //Subroutine 1659, 1727 //if There's more to be done, particularly for loops because they support the break and continue statements (also implemented with JMP). But, hard part seems to be behind me. Should have an update in two weeks.
-
I haven't read all of the posts yet, so I don't know if TSL's development history was addressed. It wasn't Obsidian's fault, it was LucasArts'. LucasArts requested BioWare make a sequel before KoTOR was finished, transferred development to Obsidian after BioWare refused, kept Obsidian from speaking with BioWare, and gave Obsidian a year and a half to make the game when KoTOR took three years. The first Obsidian learned about KoTOR came from playing the game after it was released, and the company had to scrap damn near everything it had and start over. Out of 18 months, 3 were lost to learning about KoTOR, and then story and game development proceeded. This is the reason why you barely see anything from KoTOR in TSL other than the game engine and a few character and object models. Even worse, TSL was supposed to be a bigger game and the Exile was supposed to get to level 50. That was no longer possible with only 15 months of development and testing. Much of what has been restored in TSLRCM is undeveloped content that was scrapped due to time constraints. The game we're playing is less than a year's worth of work. It's amazing it's as good as it is.
-
KotOR 2 Improved AI mod and Beancounter's Hardcore mod
AmanoJyaku replied to niBBa's topic in General Kotor/TSL Modding
As far as I know, anything you can do in a DOS prompt you can do in a PowerShell prompt. And PowerShell is way better in other ways. So, no need to make changes to the Registry. Also, I try to avoid downloading things from websites as I can't trust the downloads are free of malware, or that the REG files won't do something I don't expect. No, I'm not saying I have proof of anything wrong with the link or the files posted there. I'm just saying be paranoid, because 30ish years of using the Internet says that's the best approach. And, yeah, I recognize how that sounds on a mod site. 😁 -
I just discovered Windows 10 virtual desktops...
How have I lived without them all this time???
-
Questions about editing .utc files in TSL
AmanoJyaku replied to Mellowtron11's topic in General Kotor/TSL Modding
@Crusher @DarthParametric@Kexikus -
I honestly don't know, but that's the original author's decision to make. No matter how nonsensical something might seem, you still have to respect other people's wishes. Yes, it does. And there was never any bad blood on my part. I was simply concerned darthbdaman didn't understand the consequences: years of your work was wasted, people looking forward to the mod are left without, and a rift was created with some modders. Fortunately, you can create something else in its place. To you, as well, friend! I'm writing a tool that's even more proprietary than a mod, and I plan to release its source freely along with a detailed explanation of its design methodology. So, I agree with you in spirit. Still, if someone else has restrictions on their work I will respect those restrictions. If you think that's wrong then you need to discuss that with folks. That's just how life is.
-
Glad I'm not the only one who thinks the series is overrated. I'll even say it's not good, being full of plot holes and silliness. Why did someone punch Mando in his beskar helmet, scream in pain, then try to punch him in the helmet again? Why does the series show beskar as nigh invulnerable, but has Mando hiding behind pillars to avoid being shot? Why do they make a point of explaining the reason a transport can't move quickly, then have Mando demand the transport speed up? Why the hell are they camping out in the open when there are man-eating pterodactyl things flying around?
-
Yeah, @InSidious could have just said "no, piss off" and risked looking like a selfish jerk. But they chose to explain why they were telling @Salk to stop. Next time, Salk will be more careful to respect the traditions of the mod community since they have a clear explanation of what they did wrong. I looked at the ModDB page, Salk was beyond talking and work was under way. Worse, they gave the impression they were working with all mod authors. A lot of people who were looking forward to this release are going to be let down.
-
Often, documentation is written with the assumption that the reader has some familiarity with the topic and is simply looking for a reference as a refresher on minutae. For example, the C# link shows a class definition A with an unassigned static scalar variable x, and unassigned instance scalar variable y. This is valid because it states: The default value of an int is 0. This is achieved by the compiler writing the assignment code for you, so the initialization occurs. In contrast: With the exception of the section "Try-catch-finally statements", the remainder of the document specifically uses uninitialized variables to point out their dangers. The "Try-catch-finally statements" section demonstrates a contrived (meaning, unrealistic) example of "safe" use of uninitialized variables. Documentation and tutorials use contrived examples to demonstrate how something works, but often caution such examples are not acceptable in production code. I should point out Microsoft's documentation is mostly filled contrived examples. Post one of their examples on StackOverflow and prepare to be burned. tl;dr: never assume uninitialized variables are safe. There's a mantra you should repeat to yourself: code for correctness, worry about performance later. Modern CPUs are fast, and compilers are good at optimizing your code for those CPUs. A CPU that operates at 3 billion cycles per second does not benefit from saving a few dozen cycles by omitting a single variable initialization. If the compiler thinks you can benefit, it will often make the change for you. For example, creating variables inside of loops is considered a bad practice because you're just going to create and destroy that variable repeatedly. Which is why compilers perform variable hoisting, meaning the variable is taken out of the loop and placed before it. So: int Count = 10; while (Count > 0) { int i = 0; //Do something with i --Count; } Becomes: int Count = 10; int i = 0; while (Count > 0) { //Do something with i --Count; } But, you should probably just use a for loop: for (int i = 0, Count = 10; Count > 0; --Count) { //Do something with i } Readability is a concern, yes. But, it's more readable when you give a variable a descriptive name, initialize it with an appropriate starting value, and place it where you intend to use it. In the example, I can guess Count is the number of times to execute the loop. But, I should give i a better name. Maybe it should be Meters, or Years, or Euros? Both variables are created once, and destroyed once the loop completes. See how much we can learn from this simple code fragment? I'm not sure I know what you mean by hiding variables, but if you place them where you use them then they aren't hidden. Conflicts can easily be avoided by using descriptive names. And a function should only be as large as the single task it performs. And, please, no global variables. I don't think there is ever a reason for global variables. As for memory, that's only a problem with embedded systems and some 32-bit platforms. No matter how little RAM a computer has it still has access to virtual memory. It won't crash, but it will be slow. That's the plan. I have no idea, I only learned about these tools when I answered your request for help. It's embarrassing, because I have two copies of NWN, I just need to find them...