Author Topic: Multiple Questions Asked  (Read 17181 times)

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #50 on: July 17, 2008, 08:03:56 AM »
Yeah, it helps to check what compiler stage errors occur at. Compile time and link time errors tend to be quite different. Link errors are usually caused by a mission .lib file, or forgetting to implement a function that was declared and called.


That function you posted looks correct. I just verified the address constants. I also checked the function prototype, and the calling convention also seems correct.

What if that techID isn't found? I think the function returns -1 in that case. Would a value of -1 potentially cause your code to crash?
 

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #51 on: July 17, 2008, 08:36:25 AM »
I don't really know what to say...

Quote
BEGIN_TECH "Astronomy" 02701
    CATEGORY        1
[...]
    COST            400
    MAX_SCIENTISTS  5
    LAB             1
END_TECH

Doesn't the function take the TechID as it's present in the tech file?

I put the function on its own to test, not using its result anywhere, so I don't know why it would crash because of the result...

If it won't work, I'll have to take the time and write a silly C++ program or something for turning a tech file into a sorted Technum list to work with... I just really wanted the dll to do the conversion.

Just out of curiosity, does the function return 0 or 1 for the lowest TechID value?


Hooman, since I know you look around here often enough, I'd like to ask if there's any way the Residence demand could stop affecting morale until it's researched, just like Universities, Nurseries, etc.?

EDIT: Oh, would just like to mention the following:
When using Records for buildings, even if you start making a kit or have one in the SF bay (probably needs to be bay 1, I suppose) the record will use that building and not make a new one. I know I asked something about this before in this thread.
« Last Edit: July 17, 2008, 09:37:41 AM by Hidiot »
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #52 on: July 17, 2008, 02:39:29 PM »
Quote
Just out of curiosity, does the function return 0 or 1 for the lowest TechID value?

It's a 0-based index. But, the entry at index 0 might be a dummy tech that you never research, and isn't loaded from the file.


Quote
I'd like to ask if there's any way the Residence demand could stop affecting morale until it's researched
I don't believe so. Not any easy way at least. I think you'd need to overwrite some code to do that.

Quote
When using Records for buildings, even if you start making a kit or have one in the SF bay (probably needs to be bay 1, I suppose) the record will use that building and not make a new one. I know I asked something about this before in this thread.
Say what? Do you mean the UnitRecord in the SDK? (Exported by Outpost2.exe). Or do you mean the structs used by BaseBuilder.h? What exactly is the problem? I'm not too sure what you're saying here.



As for the crash, if you give me a crash address then I can tell you a lot more about it. Having the value of EIP would be very useful here. If Windows doesn't tell you a crash address, then attach with a debugger (OllyDbg can do this), or check the Windows error report, and try to find EIP in the register dump.
 

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #53 on: July 17, 2008, 03:28:41 PM »
I meant when using RecordBuilding, the exported function... the name didn't stick to my mind at that point. And I just wanted to throw that in there so as to not forget about it, as it's pretty important for speeding the AI up a notch. It's not a problem to be solved :)

Here's what I could find by checking the Technical info on the crash:
Exception Information
Code: 0x0000005
Flags: 0x00000000
Record: 0x0000000000000000
Address: 0x0000000000000000
There's no mistake or typo, all these last 3 things have only 0's

Next comes the loaded Modules, Thread(s) with lots of hex info.

Did I get anything good or need I search further?
« Last Edit: July 17, 2008, 03:49:48 PM by Hidiot »
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #54 on: July 18, 2008, 12:47:46 AM »
No, that crash info is pretty useless I'm afraid. Although, you can get a crash address of 0 if you make an indirect call through a null pointer. It doesn't crash until after the EIP has been updated in that case. Not exactly very useful for debugging.

If you have OllyDbg installed, then attaching to the crashed program can provide a lot of info. If you do get a crash address of 0, then the information on the stack will be very useful. Particularly return addresses. That's pretty much the only way to find out where the last call came from. Keep in mind that not all things marked as return addresses on OllyDbg actually are return addresses. It basically guesses, depending on whether or not the value on the stack is a potentially valid address in a code section.


The Thread section contains a register dump. That might be useful. I just forced a crash, and I noticed the EIP value listed there doesn't match the address given above.

At any rate, the crash address will only really be useful if it starts with 4 or 5 (since that range would put it in Outpost2.exe), or maybe 11 or so (which would put it in the DLL), but I don't have your code so I couldn't check into a crash in your DLL. If the crash address is 0, then try to find a similar value on the stack. Something with 6 non-zero digits, where the first digit is 4 or 5.
 

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #55 on: July 18, 2008, 04:18:43 AM »
How's about... I attach the source,map file and techtree (to avoid complications)?


Sorry, but I don't think I can get it right myself...
« Last Edit: July 19, 2008, 04:31:14 AM by Hidiot »
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #56 on: July 18, 2008, 01:01:54 PM »
Hmm, this caught my eye (near the very end):
Code: [Select]
    if(Rectenum.GetNext(RobS)==1)
You could have just written:
Code: [Select]
    if(Rectenum.GetNext(RobS))

In fact what you have written is bad for one simple reason. The return value is an int, but it's interpretation is as a boolean value. In that situation, any non-zero value should evaluate as true. But if you compare (value == 1), then you've change the truth value. By writing that, you make all non-1 values false. In other words, you've altered the truth values for all integers other than 0 or 1. It's good practices to never compare a boolean value against 1. (If you have to, use != 0).


You're using global variables here:
Code: [Select]
IUnit x,cmine,csmelter,structf,structf2,vechiclef1,vehiclef2,Truck1,Truck2,Convec,Earthw,Robos,Robom,bLab;
Unit ConVec,Earthy,Dozer,RM,RS,Truc,Truckf;
int i,a,a1,a2,lm,lms,lq,ls,lv,re,metnum;
int nAS = *(int*)(0x56EF1C + 3108 * 0 + 0xA8);
//   int nAS = *(int*)(0x56EF1C + 3108 * 1 + 0xA8);

Two things bad about this. First, is that some of these variables probably need to be saved and restored when the player Saves the game and Loads it again. To do this you need to place them in a struct, and then declare an instance of the struct.

Code: [Select]
struct ScriptGlobals
{
IUnit x,cmine,csmelter,structf,structf2,vechiclef1,vehiclef2,Truck1,Truck2,Convec,Earthw,Robos,Robom,bLab;
Unit ConVec,Earthy,Dozer,RM,RS,Truc,Truckf;
int i,a,a1,a2,lm,lms,lq,ls,lv,re,metnum;
};

// Declare instance of struct
ScriptGlobals scriptGlobals;

Then update GetSaveRegions to use the new struct variable.
Code: [Select]
void __cdecl GetSaveRegions(struct BufferDesc &bufDesc)
{
bufDesc.bufferStart = &scriptGlobals; // Pointer to a buffer that needs to be saved
bufDesc.length = sizeof(scriptGlobals);   // sizeof(buffer)
}
Oh, and btw, you have the bufDesc.length set to 8000 for no real reason.

The other thing that was odd about your globals, was the initialized one.
Code: [Select]
int nAS = *(int*)(0x56EF1C + 3108 * 0 + 0xA8);
This will, when the DLL is loaded into the process address space of OP2, try to read Player[0].numAvailableScientists. This would happen before InitProc, and quite possibly before that Player object is even initialized. Plus, there's no reason to cache that value as it will change over time. This initialization is really silly. I wouldn't even declare the variable here, since for it to be useful, you have to read the value just before you use it. In that case, if you're going to cache the value, do it in a local variable, not a global one.


Oh wow. This is actually kind of funny:
Code: [Select]
for (metnum=0;metnum<=6+TethysGame::GetRand(6);metnum++)
What this will do, is generate a new random number each time through the loop, and then check if it should stop looping. This will actually decrease the expected number of meteors as the distribution is no longer uniform. For instance, you do not have a 1/6 chance of getting 12 meteors. You will only get 12 meteors if the random number generator produces a 5 the last time, at least a 4 before that, at least a 3 before that, .... Also, for the first 7 loop iterations, it will produce random numbers that don't do anything.

I think what you might have intended, is this:
Code: [Select]
int numMeteors = 6 + TethysGame::GetRand(7);
for (metnum=0; metnum < numMeteors; metnum++)
This will produce a uniformly distributed number of meteors from 6 to 12. Note that I changed the "<=" to a "<" and "GetRand(6)" to "GetRand(7)". If I hadn't changed these, then it would have produced from 7 to 12 meteors. Remember that the argument to GetRand is the range of numbers produced, and they will be from 0 to (range - 1), so GetRand(6) would produce numbers 0..5. Also, if you'd like to count out a specified number of loop iterations, the usual idiom is to use "<", and start counting at 0. (Instead of using "<=", and startnig the count at 1).

You have more global variables, seperated from the rest. Dump these in the struct.
Code: [Select]
//Groups
MiningGroup com1;
MiningGroup com2;
MiningGroup com3;
// ...


Code: [Select]
SCRIPT_API void StLab()
... the patron saint of research


You have some odd abbreviations and such. You also have a bunch of strings/function names in all lower case instead of mixed case. It makes it a little harder to parse the words and read. Plus, functions are usually verbs, and variables are usually nouns. You also use rather inconsistent indentation. (Or is that because of a different editor?) Oh, and this makes me cringe. It looks like it could actually be a typo:
Code: [Select]
SCRIPT_API void Nurseri()



Gah, and another project that uses IUnit. I don't have this library, so I can't compile these projects as is. But, after some editing, I do indeed find some odd behavior when that function is called. It doesn't crash, but it does cause empty log messages to be added instead of some text string. Hmm, I'll have to look into this further.
 

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #57 on: July 18, 2008, 01:13:47 PM »
Eh, I know I'm very disorganized and do silly... very silly stuff.

I'll look at using cases from now on and will also try to tidy up that code. The names I place for variables or triggers are deliberate. If it's a typo it is most probably not used anywhere else.


Oh, and don't forget, I already warned people about the following in the first post:
Quote
Warning: Content might cause head aches, severe migraines or a possible stroke to any experienced map coder!
« Last Edit: July 18, 2008, 01:14:56 PM by Hidiot »
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #58 on: July 18, 2008, 01:45:53 PM »
Ahh, got it. The calling convention was wrong. The function was written so the callee pops the arguments off the stack (stdcall), but the compiler assumed the caller pops the arguments off the stack (cdecl).

I suppose I should have realized that. The default is cdecl, but class member functions tend to use stdcall, as do many other functions, such as the Windows API, and most of the functions in Outpost2. The functions in Outpost2 that are cdecl seem to be mostly C standard library functions. (C, not C++).


Code: [Select]
int __declspec(naked) __stdcall GetTechNum(int techID)
{
__asm
{
  MOV EAX, [ESP + 4]
  MOV EDX, 0x472D90
  PUSH EAX
  MOV ECX, 0x56C230
  CALL EDX
  RETN 4
}
}


What was happening, is that they were both popping the argument off the stack (the calling function, and the called function). Since too much got popped off the stack, values were lost and the stack got corrupted/offset. When that happens it usually crashes when it tries to return, since the return address isn't where it expects it to be.
 
« Last Edit: July 18, 2008, 01:47:57 PM by Hooman »

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #59 on: July 18, 2008, 02:40:11 PM »
Declaring all those variables inside the struct makes them unrecognizable for the rest of the file.

Also, the research works fine now. Finally, I can get on with this :)

Hooman... I am promoting you to the rank of Coding God in my list :D
"Nothing from nowhere, I'm no one at all"

Offline BlackBox

  • Administrator
  • Hero Member
  • *****
  • Posts: 3093
Multiple Questions Asked
« Reply #60 on: July 18, 2008, 04:43:27 PM »
Quote
Declaring all those variables inside the struct makes them unrecognizable for the rest of the file.
Easy fix. Just reference them with structVar.memberVar.

i.e. if you have
Code: [Select]
struct ScriptGlobals
{
int something;
};

// Declare instance of struct
ScriptGlobals scriptGlobals;

and you were previously just accessing it as 'something', change that to:
Code: [Select]
scriptGlobals.something

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #61 on: July 18, 2008, 04:45:48 PM »
You'll have to prefic the variables with the struct variable name.

Code: [Select]
ScriptGlobal scriptGlobal;
...
scriptGlobal.roboSurveyor.DoMove(...)


Edit: typo.
Seems BlackBox beat me to this.
 
« Last Edit: July 18, 2008, 04:46:35 PM by Hooman »

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #62 on: July 18, 2008, 05:34:54 PM »
Ok, I'm done for today...  guess I should have studied more by myself. Another black dot on the board for school <_<

Now on my to-do list:
Making a research list for a more streamlined research pattern.
Finding out how to keep the lab researching, as I've stumbled on something that's bugging me: The 2708 and after researches refuse to start, whilst researches 2701 to 2705 and 2707 research like they should, despite the exact same code used for all of them. In some versions of the dll, 2707 would be the one where the researching stops, despite no modifications to the research. The .GetBusy() I'm using to verify the state of the lab to put it to work if it's idling comes to mind.


EDIT: It seems very odd, but all researching seems to stop after mark 100...
« Last Edit: July 19, 2008, 04:31:02 AM by Hidiot »
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #63 on: July 19, 2008, 07:37:23 AM »
Hmm, that is odd. I might take another look at it. Is this an existing problem, or a new problem?


Well, you started me rambling on some ideas I had about research. It got kind of long, so I decided to start a new thread. (Plus it seems to be of fairly general interest, and not specifically related to your level). You can read it here:
http://forum.outpost2.net/index.php?showto...indpost&p=64728
 

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #64 on: July 19, 2008, 10:15:16 AM »
It's probably a new problem, as I couldn't get to mark 100 before with automated research anyway. I'll upload the current source and tech tree now, as I believe you still have the .map file (edited the tech file a bit today)


Hmmm... Is there a limit to the number of times a function can be accessed via the AIProc?
« Last Edit: July 20, 2008, 09:18:10 AM by Hidiot »
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #65 on: July 21, 2008, 06:42:39 PM »
Ok, the problem was traced back to running out of triggers. (Actually, it's more like running out of ScStubs, which Trigger derives from). In AIProc, it detected when the lab was free, and setup a time trigger that would start the next research in 50 ticks. During those 50 ticks, 13 time triggers were created, one for each time AIProc was called before the lab got put to use 50 ticks later by the first time trigger to execute. For each time trigger, two ScStub derived classes are created. If I remember right, one was the time trigger, and the other was a stub that handled calling the callback function in the DLL. This used up 26 ScStub entries in an array that can support a maximum of about 255. When each of those time triggers fired, it basically calls Disable(), since they were not set to repeat. They did NOT call Destroy(). If Destroy is not called, those ScStub entries are never freed, and eventually the table fills up, and you can't create anymore. Any attempt to create more just returns a Trigger stub with ID 255, which you can't do anything useful with. When that happens, Time Triggers are no longer created, and your callback that starts the next research stops being called.

If you use a Time Trigger for a one-shot event, you should probably keep track of the Trigger reference by storing it in that ScriptGlobal struct, and in the callback that it causes to fire, call trigger.Destroy(). Mind you, you should still avoid creating all those extra triggers meanwhile to start with. If you're ever low on triggers, creating that many at once could cause problems. At any rate, you'll still need to call Destroy to prevent eventual problems.

Alternatively, you can use a repeating time trigger, since in this case you actually do want to reuse the trigger, and have it repeat it's action. Then you don't need to Destroy it, and then Create it again. What you can do is setup the trigger in InitProc, or similar, and have it set to repeat. Then, in the start of the callback, you can check if the lab is busy, and if it is, just return from the callback without doing anything. There would be one slight difference this way though. Instead of starting the next research topic 50 ticks after the last one finishes, it will start the next topic on a multiple of 50 ticks after the last one finishes. This might be right away, or it might be up to 50 ticks later. It will in general not be exactly 50 ticks though.

Another solution for your situation, is don't change any code except where it creates the Time Trigger, and instead of delaying the call to your function by 50 ticks using the trigger, just call the function directly, and have it execute right away. As soon as one tech finishes, it will immediately start the next one.

Code: [Select]
  if (!scriptGlobals.bLab.GetBusy() && !Player[0].HasTechnology(3004))
  {
   //Trigger &BasicResearchTrigger = CreateTimeTrigger(1,1,50,"BasRe");
   BasRe();
  }

You might also consider using Research Triggers. Those will fire exactly when a research topic completes. If you still want to delay by exactly 50 ticks, then you can use both a Research Trigger and a Time Trigger. When the topic you are researching completes, the Research Trigger callback can start the Time Trigger. When the Time Trigger fires, it can start the next research topic.

Code: [Select]
ScriptGlobal
{
 // ...
 Trigger researchTrigger;
 Trigger researchTimeTrigger;
};

... (somehwere in InitProc)
scriptGlobal.researchTimeTrigger = CreateTimeTrigger(true, false, 50, "StartNextResearch");

... (new function)

SCRIPT_API void StartNextResearch()
{
 int bFailed = true;  // Default assumption
 // Determine next research topic
 int techID = ...;
 int techIndex = GetTechNum(techID)
 // Start the next research here ...

 // If failed, abort here without adjusting further settings
 // (Although, you can Destroy the research trigger if you want)
 if (bFailed)
  return;  // Will try again in 50 ticks

 // Disable the Time Trigger  (It is auto reset to a 50 tick delay, that starts when it's next enabled)
 scriptGlobal.researchTimeTrigger.Disable();
 // Set the Research Trigger
 if (scriptGlobal.researchTrigger.IsInitialized())  // Only destroy it if it's already in use
  scriptGlobal.researchTrigger.Destroy();  
 scriptGlobal.researchTrigger = CreateResearchTrigger(true, true, techID, playerNum, "ResearchComplete");
}

SCRIPT_API void ResearchComplete()
{
 // Start the next topic in 50 ticks
 scriptGlobal.researchTimeTrigger.Enable();
}


This will wait 50 ticks, and then start the first research. Whenever a topic completes, it will wait 50 ticks before starting the next. If it is unable to start, because the lab is destroyed, or disabled, then it will keep checking if it can start a new topic every 50 ticks. Note that the Time Trigger is never destroyed. It is only Disabled and Enabled. The Research Trigger needs to update which topic it is watching, and so you need to recreate this one, which means you need to Destroy the old one.


The next part is to simplify how you specify the list of topics to research. You'll want to put them in an array of some kind.
Code: [Select]
int researchTopicList[] = 
{
 2701,
 2702,
 // ...
};

You might also consider using an Enum, so the list is easier to read. Since you're designing your own tech file, you'd also have to write your own enum to do this.

Also, you might want to have two research lists if you AI can be either Eden or Plymouth. Remember that they can get different techs, or have different costs for researching each tech. It would then make sense to define two arrays if your AI needs to be able to play as either.
Code: [Select]
int edenResearchTopicList[] = 
{
 // ...
};

int plymouthResearchTopicList[] =
{
 // ...
};

Of course there is no need to stop there either. You can have more arrays if you want. Maybe you want an AI to have a random personality. Perhaps it's more war like one round, and another it's racing to launch a spacecraft, or just building up it's base.

Next you'll need a way of traversing the list. For this you can add another variable to the ScriptGlobal struct. One that keeps track of the research topic index.

Code: [Select]
ScriptGlobal
{
 // ...
 Trigger researchTrigger;
 Trigger researchTimeTrigger;
 int researchTopicIndex;
};

// In InitProc
scriptGlobal.researchTopicIndex = 0;  // Initialize to start of list

Then, each time you start a new research (successfully), you need to increment that value.

Code: [Select]
SCRIPT_API void StartNextResearch()
{
 int bFailed = true;  // Default assumption
 // Determine next research topic
 int techID = researchTopicList[scriptGlobal.researchTopicIndex];
 int techIndex = GetTechNum(techID)
 // Start the next research here
 if (techIndex != -1)
 {
  // Start the research (with as many scientists as possible)
  int maxScientists = GetMaxScientists(techIndex);
  int numAvailableScientists = GetNumAvailableScientists(playerNum);
  int numScientists = Min(maxScientists, numAvailableScientists);
  scriptGlobal.lab.Research(techIndex, numScientists);

  bFailed = false;
 }

 //... Rest of function, as above
}

SCRIPT_API void ResearchComplete()
{
 // Increment the topic index
 scriptGlobal.researchTopicIndex++;
 // Start the next topic in 50 ticks
 scriptGlobal.researchTimeTrigger.Enable();
}



What's still left to do, is terminating the research cycle once the list is complete, and determining the max number of scientists that you can assign to each research topic. There is a way to read this value from the in memory structs, so I'll provide a function to do that. Then you don't need to specify it in a seperate list, which is also error prone, especially if the tech file is updated.

Note: This following code to retrieve the max number of scientists you can assign to a research topic is untested.
Code: [Select]
int __declspec(naked) __stdcall GetMaxScientists(int techIndex)
{
 __asm
 {
  MOV EAX, [ESP + 4]
  MOV ECX, [(0x56C230+4)]  // Load TechInfo*[]*
  MOV EDX, [ECX + EAX * 4] // Load TechInfo*  (of techIndex)
  MOV EAX, [EDX + 0x14]  // EAX has return value of TechInfo.maxScientists
  RETN 4
 }
}


Now, you just have to make sure to terminate the research system when the list is complete. First, you'll need to know how big your list of topics is. There is an easy way to get the compiler to figure this out.
Code: [Select]
int researchTopicList[] = ...
int numResearchTopics = sizeof(researchTopicList)/sizeof(researchTopicList[0]);

Now, when the topic is incremented to this value, you need to shutdown the trigger system so it doesn't walk off the end of your list.
Code: [Select]
SCRIPT_API void StartNextResearch()
{
 if (scriptGlobal.researchTopicIndex >= numResearchTopics)
 {
  // Shutdown trigger system. All research has been completed.
  if (scriptGlobal.researchTrigger.IsInitialized())  // Need to check this in case the list was empty (and good habit)
   scriptGlobal.researchTrigger.Destroy();  // Don't need this anymore
  scriptGlobal.researchTimeTrigger.Destroy();  // Don't need this anymore either
  return;  // Don't try to do anything more
 }
 // ... Rest of function, as above
}

Note that I chose to destroy the trigger when it tried to start a new topic, after the old one finished, rather than immediate after the last topic started. This is because that last topic might not finish, if say the lab is destroyed. We want it to be restartable. This is also why I chose to increment the topic index after the research had been completed. If some topic doesn't complete, we don't want to skip over it and trying researching the one after it. Also, if the list of topics is empty, I want to system to just exit gracefully. Hence why I put the termination condition at the start of StartNextResearch, rather than after the increment in ResearchComplete.


There are a few problems that I'll leave you to solve. First, what happens when a lab is destroyed? How will you restart things and continue with the research? (I built the code so that this will hopefully be easy to do). You also need to figure out how the system will work with the 3 different lab types. As I wrote it, it assumes a single lab type for all research topics. You could use 3 seperate lists, but that won't guarantee you don't start a research topic until you're finished the prerequisites. You could also check which lab type a topic needs (I can provide code to get this), and then see if the variable for that lab type is set. If it is, start the topic at that lab. That would make the research topics serialize nicely, and should prevent weird dependcy bugs, provided your list of topics is correct. It's probably a little less obvious how to do research in parallel this way though. See if you can find a solution.


Also, I'd like to include the following disclaimer. I haven't tried out any of this code. I can't even guarantee if it will compile. Try it and find out for yourself. I've also been editing it as I go along, so it might not even be consistent.
 

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #66 on: August 17, 2008, 01:44:37 PM »
Ok, so I tried adding those things and ended up wit hthe following pretty interesting errors:

|28|error C2059: syntax error : '{'
|28|error C2334: unexpected token(s) preceding '{'; skipping apparent function body
|301|error C2039: 'BasicResearchLine' : is not a member of 'scriptGlobal'
|307|error C3861: 'Min': identifier not found, even with argument-dependent lookup

line 301 error is obvious, as it's related to the errors on line 28, which is the Basic ResearchLine declaration that goes like this:
   struct scriptGlobal
   {...;
    int BasicResearchLine [] =
/        {
        2701,
        2702,
        2703,
        2704,
        2705,
        2707,
        2708,
        2709,
        3004
        };
    ...;
    };

Line marked with "/" is line nr. 28

Nice of it to refuse the declaration of an array in that struct.

The error on line 307 is also pretty interesting...
« Last Edit: August 17, 2008, 01:45:15 PM by Hidiot »
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #67 on: August 17, 2008, 06:08:53 PM »
Don't declare the array in the struct.

First reason: It doesn't make sense to do so. The struct is only a data type, and not something that has memory at runtime. Hence, you can't assign to any of it's fields. Instead, you must create an instance of that struct, and assign to the fields of the struct instance. (A struct instance does have memory). This is the difference between:
Code: [Select]
struct ScriptGlobal
{
// ...
};
and
Code: [Select]
ScriptGlobal scriptGlobal;

Second reason: You don't want to do this anyways. You don't need to save constant values, so they don't need to be stored in the script global struct. Just place the array before or after the struct. What you do want to save in the global struct, is the current index into your array.


Min isn't a built in function. You'll either need to write it yourself, or find some standard library header to include that contains it. It's not very hard to write though.
 

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #68 on: August 18, 2008, 02:53:53 AM »
Ok, fixed everything.

There is a crash and it comes from:
        int maxScientists = GetMaxScientists(techIndex);

The GetMaxScientists is the same one you gave me.

EDIT: there's a second crash spot I found: scriptGlobal.researchTimeTrigger.Disable();
« Last Edit: August 18, 2008, 03:20:24 AM by Hidiot »
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #69 on: August 18, 2008, 06:05:36 AM »
Hmm, try this for a replacement:
Code: [Select]
int __declspec(naked) __stdcall GetMaxScientists(int techIndex)
{
__asm
{
  MOV EAX, [ESP + 4]
  MOV ECX, 0x56C230+4   // Load &Research.TechInfo*[]*
  MOV ECX, [ECX]    // Load TechInfo*[]*
  MOV EDX, [ECX + EAX * 4] // Load TechInfo*  (of techIndex)
  MOV EAX, [EDX + 0x14]  // EAX has return value of TechInfo.maxScientists
  RETN 4
}
}

It seems that the inline assembly for:
Code: [Select]
MOV ECX, [0x56C234]
results in the same instruction as:
Code: [Select]
MOV ECX, 0x56C234

The second one clearly means to load a constant value into a register. The first form should, according to the rules I'm most familiar with, be a load from a memory address. Seems that's not the case. I'm most familiar with NASM style assembly. The C++ inline assembly seems to be based on MASM style assembly though. Mind you, I would have thought they would be the same for something like this.



As for the second case, some crash details would be helpful.
 

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #70 on: August 19, 2008, 03:31:32 AM »
I just switched to a new computer yesterday and I found out I might need to re-format to add a partition on my only installed hard drive, thing that I don't even know how to do...

Bottom line, it'll be another few days until I actually get my coding back up again.
« Last Edit: August 19, 2008, 03:50:35 AM by Hidiot »
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #71 on: August 23, 2008, 05:27:50 AM »
I didn't look too closely, but I did see something you should fix that could cause a crash.

In AIProc, you have:
Code: [Select]
  Trigger &BasicResearchTrigger = CreateTimeTrigger(1,0,60,"StartNextResearch");

Remember that AIProc runs every 4 ticks. This will likely spam the engine with lots of identical triggers, and eventually you'll run out. When that happens, the game will crash. It might not crash here though. It might crash elsewhere if you try to create a trigger.

Same for the line in the following block:
Code: [Select]
  Trigger &StandardResearchTrigger = CreateTimeTrigger(1,1,50,"StdRe");


Hmm, actually, come to think of it, the line you identified as the cause of the crashes would likely only crash if that variable wasn't initialized, or if it was already destroyed. From the way the function is written, it's basically assuming that trigger was the one that caused entry, but in this case, it isn't. The "BasicResearchTrigger" temporary that you created in AIProc is the one causing entry. You should have stored the created trigger in the scriptGlobal struct.

Code: [Select]
scriptGlobal.researchTrigger = CreateTimeTrigger((1,0,60,"StartNextResearch");

Same with the other line.
 

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #72 on: September 11, 2008, 07:47:48 AM »
I've been looking a bit and have noticed the lack of creation for the researchTimeTrigger.


The trigger shouldn't get called more then once(or a few times, due to the delay) each time the lab is found to be idling (not researching or being built or etc.)
I think I'll remove the time delay.
Is there any of immediately activating a SCRIPT_API without using a trigger?

Every time something related to the use of researchTimeTrigger tries to do something, it crashes... I think I might have misunderstood its use, so will look into it deeper to rename it or create it.


EDIT: Ok, I've fixed those crashes by realizing the time trigger was the trigger I used to initiate all research :P

Now I notice that the only thing that keeps my researches working is the initial call itself... re-enabling the trigger won't make the researches go on.

I can't do much else anymore now, so I'll leave the file for further inspection
« Last Edit: September 11, 2008, 08:15:36 AM by Hidiot »
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #73 on: September 20, 2008, 05:48:25 PM »
I made some changes and simplifications to your code in the SVN repository. I did it step by step, and commited after each step. You can check the log messages to see what was changed (TortoiseSVN -> Show log), and you can also see a Diff of each successive change (right-click on the revision -> compare with previous revision, double click a changed file to view (main.cpp)).

If you like the changes, then you should run "SVN Update" on your working copy. If you only want some of the changes, you can update to a specific revision (TortoiseSVN -> update to revision...). This also lets you backdate your working copy should you run "SVN Update" first, and then change your mind. The "View log" button will help you select a specific revision.


If you don't like any of the changes, then you can revert the changes (basically commit a previous revision as the new revision). Or if you've already made edits to your local copy, you may need to do a merge. In either case, either bug me on IRC for help, or read the Subversion book (or equivalently, the help file that comes with TortoiseSVN).


Edit: Oh, and I should mention that I only tested to make sure the file still compiles. I didn't check if it runs the same, or even if it runs at all.
« Last Edit: September 20, 2008, 06:16:25 PM by Hooman »

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #74 on: September 21, 2008, 05:46:02 AM »
All the changes are for the better so I used them.

Only problem upon compiling is the following:

Main.cpp|499|error C2679: binary '=' : no operator found which takes a right-hand operand of type 'Unit' (or there is no acceptable conversion)|

That's for this line:
scriptGlobal.bLab = GetFreeUnitAt(mapBasicLab, 51+31, 30-1, 55+31, 34-1);

I even changed the "unit" variable from GetFreeUnitAt to a IUnit type (cause bLab is IUnit also), but still nothing.


I've made a lot of trigger removals, though I suspect it might not have been wise. Those that were a one-fire will probably go back and I'll give handles stored in scriptGlobal so I can destroy them after usage, not just disable them.

The scripts that need to run repetitively will get trigger removals that I hope will stay. Might save some headaches with trigger destruction and re-enabling.
"Nothing from nowhere, I'm no one at all"