Author Topic: Multiple Questions Asked  (Read 17180 times)

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #25 on: October 17, 2007, 05:15:03 AM »
Ok, first of all, using record building will build the buildings in the order you record them in. If a buildnig is destroyed, it will rebuild the one that's first in it's list before continuing on.

The built in levels usually record buildings in batches. It takes a lot less code that way. You seem to be using triggers very excessively in your code. Just add them in batches, and they will be built in the order they were recorded in. The only reason to really use time triggers, is to control batch buildings, where there is a fairly lengthy delay in the build up. Such as, the main base is expanded from it's initial form, then a second base is built, and then the main base is exapnded further, at some later point after the expansion base is setup, then maybe another base is built up, and then the main base starts expanding further. If you recorded all buildings at once, each building group would build in parallel, trying to finish (almost) as fast as it could. By delaying the record building for a batch, you can add delays between when one expansion is finished and the next is started. There is no point to adding a record building as soon as the previous building is done, as it would have the same effect as them both being recorded at the same time.
 

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #26 on: October 17, 2007, 10:02:50 AM »
The time triggers are currently just there to get a feel of the AI... I plan on using Research triggers a lot, cause that's pretty much how a human player does it... build stuff just as it has researched it.

I'll look into the RecordBuilding order next time I get the time.


Buut... in order to test the map further, I need a fix to the researching problem. What's wrong with it, that the game crashes right when the first research should begin?


EDIT!: I've worked around with the researching and have come to the latest version. Check it just before the InitProc()...

Unfortunately, I can't tell if it is working or not. It has made the game stop crashing, but I fear there is no progress being done at all now :/
« Last Edit: October 17, 2007, 11:01:03 PM by Hidiot »
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #27 on: October 18, 2007, 07:03:57 AM »
I just noticed this one line here:
Code: [Select]
if (Player[1].HasTechnology(2701)) TethysGame::AddMessage(1+31,1-1,"IT IS WORKING!",0,0);

This seems sort of silly to have in InitProc, since it only ever gets run once at level startup. If you set the player to have that tech, then it'll print, otherwise it won't. You won't get a message later on in the game when they research it. (You can use a research trigger for that).


Also, you're code is getting rather large, and also kinda far back in the thread, making it a little harder to reference. It would help if you'd include the relevant code in replies, and try to keep it down to a minimum of the area we need to focus on. (We still need to see how things are defined and all, but maybe cut out stuff irrelevant to the current topic).


I'll look back later on. I'm pretty tired right now. Seriously overworked with school. :(
 

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #28 on: October 19, 2007, 04:53:06 AM »
Ok, there are a few issues I can see.

First of all, Player.Scientists() returns the total number of scientists, not the available number of scientists. You could still be trying to assign too many.

Secondly, lift the check for Scientists() > 0 out of that mess, since it's common to all. It would make the code much more readable.

Replace your rather sick looking nested if statements with something simpler. Generally you should use { } to disambiguate things, rather than "else;". What you can do is use (or create) a "min(int a, int b )" type of function. Better yet, if the library you're using allows you to quesry a tech for the max number of scientists for a research, is to combine this with a min function and wrap all that into a "DoResearch" type function.

Ex:
Code: [Select]
// Note I'm using some fake/nonexistent function names in here
lab.DoResearch(int techID)
{
  int availableScientists, maxAssignableScientists, numScientists;

  // Get available scientists
  availableScientists = Player[lab.ownerNum].GetAvailableScientists();
  // Get max scientists for research
  maxAssignableScientists = Technology.GetMaxScientists(techNum);

  numScientists = min(availableScientists, maxAssignableScientists)

  lab.Research(techID, numScientists);
}


My next suggestion is to get rid of the cascaded if statements all together. Use an array of some kind, and a global counter variable that tells you how far into the list you've researched. (Put it in a global struct that gets saved by GetSaveRegions). Then, use the same function as the callback for all research being completed. Each time it's called, it increments the index of the research being completed, and if it's not passed the end of the list, it'll start the next research.

Ex:
Code: [Select]
// Global AI research order array
// Note: I STRONGLY suggest using symbolic names here,
//   such as those listed in OP2Helper\EnumTechID.h,
//   or something equivalent for tech files that you wrote.
//   (But, here is a numeric exmaple instead)
int AIResearchList[] = {
  2701,  
  2702,
  2703,
  ...
};

// Trigger callback function
SCRIPT_API void AIResearchCallback()
{
  if (scriptGlobal.AI.researchIndex < SizeOfResearchList)
  {
    lab.DoResearch(AIResearchList[scriptGlobal.AI.researchIndex]);
    scriptGlobal.AI.researchIndex++;
  }
}

Ideally however, you should make sure the DoResearch was actually successful before incrementing the researchIndex. If no scientists were available or the lab is destroyed or disabled or whatever, then the AI will stop researching. You may need to design something to jump start research again if it ever stops. Remember that just because you order a lab to start researching something, it doesn't mean it will necessarily finish. Prime example is if the player destroys their lab. In which case the research callback will never get called to start the AI on it's next research, so all the research will just hang.

Also, you'll need to start off the first research somewhere outside of the callback. (You should probably also initialize the researchIndex to 1, and have the initialization research element 0 in the list).


As for the crash, I suspect maybe the enumerator failed to find the lab. I have no idea what sort of error checking is done on the lab functions from that library you're using. It might just assume that it's always passed a valid lab. If it's not, then it could probably crash the game. There are two possible problems I see with your code for finding the lab. The first one is, it doesn't loop, so if it finds another unit first, then it won't check again for the lab. This could happen if the rect you're using to check in is too big, and another unit moves into that rect. The other problem, is there is no error reporting if it doesn't find a lab. It could be that your rect is wrong, or the lab is destroyed, and no unit at all is ever found. In which case the if is never executed, and your bLab variable is never properly initialized.
« Last Edit: October 19, 2007, 04:56:55 AM by Hooman »

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #29 on: October 19, 2007, 10:49:51 AM »
Well, the code I use to find the basic lab is the same I used for the Vehicle Factory, and that one worked.

The rect is pretty much set to cover the whole building and adjunct tiles, as I don't know what it looks for when spotting a building.


If I'm not dead by tonight, I'll try to work on those improvements you suggested. Me... I'd just be happy if the damn thing would work :/
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #30 on: October 20, 2007, 04:35:32 AM »
Why not just use a building enum? It'd probably be much easier. Then you can be sure it only returns labs of the given type owned by a certain player. The only thing you really need to check then, is if the lab is busy/building/dismantling. Maybe check the location as well if you have more than one lab of the same type for the AI.
 

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #31 on: October 20, 2007, 12:48:54 PM »
hmmm... I might check a few things tonight, when everything is over... Couldn't work last night, cause I was nearly dead (really)

Whilst reading and understanding what you said, I can say:

1. the basic lab SHOULD be taken the right way. It checks if the variable basiclab is mapBasicLab.

2.

Code: [Select]
    if (Player[1].Scientists()<5) if (Player[1].HasTechnology(2701)==0) bLab.Research(2701,Player[1].Scientists()); else;
       else if (Player[1].HasTechnology(2701)==0) bLab.Research(2701,5);

It might be my fault for showing all the code there, but if we take one example, like for the first tech, I made it with the followoing in mind: the SCRIPT_API void Basicres() contains all the researches in the basic lab, and the trigger that calls it is a tiime trigger that runs every 4 marks, to ensure a restart of a research in case the lab has been destroyed (gonna go do a few things, to make the trigger stop in case the lab was destroyed and restart after it hs been rebuilt. that might cause a problem or 2 later on). Then I used that jumbled up if soup to ensure all conditions are met (for when I will eliminate the Player[1].GoAI; I'll do a funtion that always gets the number of scientists assigned, by getting the number of buildings that use scientists, get the number of used scientists, subtract them from Player[1].Scientists() and get the number of free scientists. For now, I am 100% sure the AI has over 255 scientists though, and that is surely enough. Then it also checks if the AI has over 5 scientists. If it does have over 5 scientists, it will assign exactly 5 scientists to the research.

EDIT: I've found a way to make the nest look nicer:
Code: [Select]
    if (Player[1].HasTechnology(2701)==0) if (Player[1].Scientists()<5) bLab.Research(2701,Player[1].Scientists());
                                          else bLab.Research(2701,5);

The structure I use is to se if I can get ti to work. Efficiency in the first run isn't my goal. To finish first, first you must finish... And I only want to see this code soup work! Then I'll make it work fast and efficient.
« Last Edit: October 20, 2007, 01:35:46 PM by Hidiot »
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #32 on: October 21, 2007, 04:48:31 AM »
It wasn't so much a question of fast or efficient. Just easy to write and maintain. The main reason for writing it like I suggested, is it's easy to get it right. Plus, it's easy to edit without introducing bugs when you change things. It also tends to save typing, which makes it a little more pleasant to write in the first place.


As for the possible bug in your code....
Code: [Select]
 if(Rectenum.GetNext(basiclab)==1)
       {

right there. If it doesn't find anything, it returns 0, and then your check for mapBasicLab is never run. Or any of the other code either, such as the assignment to bLab from basiclab.
 

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #33 on: October 21, 2007, 08:09:45 AM »
Had a few thoughts.

First, the time trigger would work. You might want to try to find the lab in the time trigger itself rather than use a stored value. You would have to check that a stored value is still valid before using it anyways, so why not just find it while your at it. Or, probably a little better efficiency wise, is to store it once you've found it, and use the stored value for as long as it's valid, and if not, then search for a new lab unit. If one can't be found, then abort the rest of the trigger. (Return).

In the code I posted, I was just thinking I never setup a ResearchTrigger at the end of my procedure. I should probably have done that when I told the lab to start researching. That would of course make sure the research callback keeps getting called whenever research is done. This is possibly more efficient than a time trigger, but not really with the way triggers are implemented by OP2. It was fairly natural as long as research doesn't get interrupted, although, the time trigger way can auto recover from research interruptions. Might as well use a time trigger.

Also, don't bother trying to turn the trigger on and off. The amount of processing time saved by turning the time trigger off is negligible. Trying to pause and restart it with a complex system of triggers will likely turn out to be much less efficient. Just try to keep your callback small, and have short circuiting returns when no work needs to be done. You don't want to be spending a lot of time in the callback if it's not going to do anything that tick.


The game already calculated the number of available scientists, as well as the number of available workers. Maybe consider using the ones the game already calculates for you. I suppose the game might not bother calculating them for a player with GoAI() set for them, but I haven't really checked. Anyways, the offsets of the available number of workers and scientists in the Player record are 0xA4 and 0xA8. The player records are 3108 bytes long. So, to get the available number of scientists you might do something like this:

Code: [Select]
numAvailableScientists = *(int*)(0x56EF1C + 3108 * playerNum + 0xA8);

 

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #34 on: October 21, 2007, 09:41:17 AM »
Hmm... The code I use to find the lab is the exact same one that Mcshay used in his working AI demo to find and use the Vehicle Factory.
I just turn the trigger off to avoid any possible stupid problems that might occur.

And I'm having a hard time understanding that callback Need mor etime to ponder upon it.

And uh... The numAvailableScientists = *(int*)(0x56EF1C + 3108 * playerNum + 0xA8); thing could work also... as long as I don't mess something up :)
"Nothing from nowhere, I'm no one at all"

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #35 on: November 03, 2007, 01:19:00 PM »
ok... I've tried many things, but nothing works.


Final stage of the code is as follows:

First, the code that finds the lab and calls the research script every mark. The code finds the lab the way it should. That means bLab is actually a basic lab.
Code: [Select]
        
if(!bLab.IsLive())
    {
        IUnit lab;
        InRectEnumerator Rectenumbl(MAP_RECT(51+31,30-1,55+31,34-1));
        if(Rectenumbl.GetNext(lab)==1)
        {
            if (lab.GetType() == mapBasicLab)
            {
                switch(lab.GetBusy())
                {
                    case ctMoDevelop:
                    case ctMoUnDevelop:
                    case ctMoDismantle:
                    case ctMoResearch:
                        break;
                    default:
                    bLab = lab;
                    Trigger &basre = CreateTimeTrigger(1,0,100,"Basre");
                    break;
                }
            }
        }
    }

And here's where it all goes wrong:
I am using that "re" out of desperation, to make sure the research isn't called 1000 times in one millisecond.
int nAS = *(int*)(0x56EF1C + 3108 * 0 + 0xA8); (Used to get the scientists... in case there are less than 5).
Code: [Select]
    if (!Player[0].HasTechnology(2701) && re==0) {if (nAS<5) {bLab.Research(2701,nAS); re=1;}
                                        else {bLab.Research(2701,1); re=1;}}
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #36 on: November 07, 2007, 06:19:56 AM »
How do you know for sure? Did you try adding some sort of output display? Is bLab really a handle to the lab?

Did you try to comment out the if block and see if it still crashes?

If the if block is causing the crash, maybe comment it out and put in a single bLab.DoResearch call with hardcoded parameters and see what happens. Play with the parameters a bit and see if out of bounds values might be causing a crash.

Maybe change the ID on bLab and see what happens. It will probably crash, but does it crash in the same manner as it's currently crashing?



Also, try putting a supernova next to the lab, and level it. See what happens then. Provided you got things to not crash up to that point of course.
 

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #37 on: November 07, 2007, 11:56:37 AM »
Uh should have been mor especific (me):

I tried everything I could:
-added output display with if (bLab.GetType()==mapBasicLab) and it returns true.
-commented it out and it stopped crashing
-changed the Id on the lab, same thing
-always crashes when it gets to the research instruction. Tried that with a time trigger.

havn't tried putting in other parameters... but if the TechID param (which is shortint in the IUnits.h) isn't the one you find the ResearchEnums, then, what makes it work?

I've been on it for more than a week and I'm rather fed up with it... I am 100% sure the bLab.Research(2701,1) is crashing. I put in just one scientists, to make sure that isn't the problem, and no, it isn't.
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #38 on: November 08, 2007, 01:23:46 AM »
Ahaha. You might want to check that assumption right there. The internal command packets use techNum, where the first item in the tech file gets 1 (I think 0 is a dummy tech, but maybe the first is 0). Does the function you're using take in techNums or techIDs? There is an internal function that translates techIDs to techNums. Maybe try using low numbers and see what happens. It might be easier to see if you're doing this for a lab that the player owns instead of the AI. Then you can see what research pops up in it. Or you can actually check the source of the function you're using and see if it does any translation. (Is the source even provided or just a header file? Well, the parameter name should at least be suggestive of what type it expects.)
« Last Edit: November 08, 2007, 01:24:22 AM by Hooman »

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #39 on: November 08, 2007, 02:05:29 AM »
The header is the IUnit.h one, with the attached commands.h  Didn't find anything much in there. No source of a working example that I can study sadly.
Code: [Select]
void Research(short techID,short numScientists);
Um... if you should happen to find what that internal convertor is (assuming it also works for made-up techs added to th tech tree) before I do, please do tell!
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #40 on: November 09, 2007, 08:50:35 PM »
It's an internal function that's not exported. It just looks through the internal data structures for the techs for the one with the given techID and returns it's index. It'll of course work for made up tech files other than the originals. I wouldn't suggest you go looking for the function yourself. Not unless you're feeling really bold and want to add a bit of assembly to your project. Although, the relevant label can certainly be found in the posted .udd file for OllyDbg if for some reason you do decide to go that route.


Just test out a few small values for the techID parameter, like 1, 2, 3. See what happens. I'm actually not that familiar with IUnit. Plus it's been ages since I've really done any OP2 programming.
 

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #41 on: November 10, 2007, 09:42:23 AM »
Uh... adding 1 gives me the "Intentionally left blank" research and adding 2 gives me the next research in numer order. meaning the TechID number 2004.


I'd like to know that internal function, even if it's ugly... I do not want to loose all my hair looking for each value I need or re-writing the tech file so the techs come in correct numerical order.
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #42 on: November 10, 2007, 02:38:45 PM »
Ok, looks like that is the problem then.

Although, one of the reasons why I said you probably shouldn't bother is that this is better handled in library code, such as an edit to the function your calling. But then I don't see Eddy around much anymore, so I doubt an update will be too forth comming.


Here's the relevant internal function:
Code: [Select]
00472D90 >/$  83EC 04       SUB ESP,4                              ;  Function: Research.GetTechNum(int techID) (returns techNum or -1)

The hidden "this" pointer should take a value of: 0x56C230
As in, that's the address of the Research object.


A typical calling sequence might look something like this:
Code: [Select]
int __declspec(naked) GetTechNum(int techID)
{
  __asm
  {
    MOV EAX, [techID]
    MOV EDX, 0x472D90
    PUSH EAX
    MOV ECX, 0x56C230
    CALL [EDX]
    RETN
  }
}

Note: I might have the __declspec in the wrong place. If the compiler complains, move it before the return type (before the "int").

You can probably guess I didn't test any of that out, so don't be too surprised if it crashes or something.



Oh, and if that techID can't be found, it'll return -1. You might want to add some error checking in your code to check for this when you use that function. If it's not -1 then it should be a valid index of a tech. Although you can probably ignore this initially when testing, but it's a bit of a bad habit. Especially since an edit to your tech file could probably crash the game if you're not checking for that.
 
« Last Edit: November 10, 2007, 02:41:31 PM by Hooman »

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #43 on: November 18, 2007, 07:23:29 AM »
The game crashes...

I tried understanding that piece of code, but I'm a bit lost...

I can't understand exactly what I'm supposed to fit in my dll and I definitely can't understand what those commands do. I tried understanding them, but I got lost... maybe if I understood more, I would have a better chance at spotting potential problems :P
« Last Edit: November 18, 2007, 07:38:09 AM by Hidiot »
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #44 on: November 18, 2007, 02:33:18 PM »

Heh, the code I posted had 3 bugs in it. Can you spot them all? :P

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

First of all, I should maybe trust the compiler a little less. With "naked" it assumes you setup your own stack frame. Not a problem, since a function this simple doesn't really need much of one. Except when I tried to load the parameter into a register, it was referenced off of EBP instead of ESP. It just assumed I'd setup EBP. Wasn't really expecting that since use of EBP for local variable access usually gets optimized away, particularly for the majority of the code I've read, but I guess using it is the default.

The next big problem was the [EDX], which was dereferencing the address of the function before calling, when it should have been calling direct. Basically it read the first few bytes of the correct function and interpreted them as the address of where it should call, then it went off to try and execute some invalid area of memory. Hence the crash.

The third problem was the RET didn't pop the parameters off the stack, thus leaving you with a corrupted stack when you returned.



Well, I tested out this version, and it works now.


Oh, btw, the techs get sorted on their techID. So the techNum won't be the order it appears in the file, but rather the sorted order of the techID for that tech. So if you checked for techAgridome (using the enum from OP2Helper, or tech 2101), it should return 2, even though that tech is listed somewhere in the middle of the file. The techID for the Spider is 2099, and it's techNum was 1.
 

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #45 on: November 19, 2007, 02:49:08 AM »
Sad thing is... it still crashes :blink:  
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #46 on: November 19, 2007, 06:33:56 PM »
Hmm, is it different code causing the crash?

Are you checking for a return value of -1 in case it couldn't find a tech with the given tech ID?


Maybe try to seperate out the code for this section into a smaller project, and if it crashes then post the code for the smaller project. (Also, try to remove the excessive "teaching" comments that we put before each function in the template).
 

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #47 on: July 16, 2008, 10:45:54 AM »
I decided to try again at working on this thing, but the new compiler keeps giving me headaches.

Like the following:
Main.obj||error LNK2019: unresolved external symbol "public: void __thiscall IUnit::Research(short,short)" (?Research@IUnit@@QAEXFF@Z) referenced in function _Basre|
Does the same for all IUnit.h functions.

I can't figure out what it wants right now...
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Multiple Questions Asked
« Reply #48 on: July 16, 2008, 10:16:08 PM »
To use IUnit.h, you also need to include the .lib file. The .h just describes the classes, but doesn't have any of the code to implement the functions. These functions are NOT exported from Outpost2.exe. They were custom built. You need to include the .lib file that Eddy-B supplied with the header, so that the linker can find the function code.
 

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Multiple Questions Asked
« Reply #49 on: July 17, 2008, 04:37:56 AM »
:huh:  d'ooh!  :wacko:  :oops:


Now why didn't I think about checking the libraries, not just the headers... Probably that headache had something to do...

Thank you Hooman!


Now I'll probably get back to that annoying researching.


I think I've found a thing that causes a crash:
Code: [Select]
int __declspec(naked) GetTechNum(int techID)
{
__asm
{
  MOV EAX, [ESP + 4]
  MOV EDX, 0x472D90
  PUSH EAX
  MOV ECX, 0x56C230
  CALL EDX
  RETN 4
}
}
With this call: GetTechNum(2701);

I've stripped everything but this in the trigger and the game crashes upon executing. Removing this last command stops the crashes.
« Last Edit: July 17, 2008, 05:35:04 AM by Hidiot »
"Nothing from nowhere, I'm no one at all"