1. Forum moved (you can use login and pass from old forum)
  2. Many discussions moved to the bugtracker

Battlescape progress and questions

Discussion in 'Coding' started by Istrebitel, Aug 26, 2016.

  1. Istrebitel

    Istrebitel Well-Known Member Official Developer Administrator

    Joined:
    Aug 8, 2016
    Messages:
    185
    Likes Received:
    83
    And thing is, here's another case
    [​IMG]
    (There is no right wall in 1,0,0, it's actually a scenery at 1,-1,0, but imagine there actually is one. I just couldn't find an example with a right wall, but if it'd be there, it would work just the same way.)

    Anyways, as you can see, in this case, we must draw 0,0,1 here after our agent, which is either in 0,0,0 or 1,0,0. However, agent must be drawn after walls in 1,0,0 and ground in 1,0,0, so the agent must be drawn in 1,0,0

    So in this case, draw order is:
    ...
    1,0,0
    ...
    0,0,1

    Which leads me to believe this cannot be handled in our current tile engine. I think we will require a proper z-level algorithm that draws everything as it would in a 3D game - object after object, based on which object is in front of which. And when drawing limited by layers (like, up to layer 3 or only layer 2), it would just skip objects not belonging to the limit.
     
    Last edited: Jun 15, 2017
  2. Istrebitel

    Istrebitel Well-Known Member Official Developer Administrator

    Joined:
    Aug 8, 2016
    Messages:
    185
    Likes Received:
    83
    also we have an error in language strings. Delay string for explosives reads msgid "Delay = %i" however it should be float as delay is in increments of 0.25, and it's missing "s." in the end
     
  3. Istrebitel

    Istrebitel Well-Known Member Official Developer Administrator

    Joined:
    Aug 8, 2016
    Messages:
    185
    Likes Received:
    83
    LINE OF SIGHT

    Well I've been thinking on this problem for some time now. Still can't figure out an elegant solution. I didn't look how OpenXCom did it, but we will probably need a different algorithm anyway. In OpenXCom, you only need to update one unit at a time (or update all units once after explosion happens or turn ends and smoke changes). Here we have to update constantly.

    Vanilla seems to have updated vision only on certain intervals. It's often a case of agent walking into a new room and seeing nothing, and then after a second or two seeing way more, including enemies that were right up his nose. In such case pausing was benefitial, waiting for the game to update LOS, to see if enemies are spotted.

    Anyways, anyone has any ideas how to implement this?

    I could of course shoot rays in a frontal cone every X ticks and see which tiles it passed through and check them as "visible", ignoring units (but adding every unit that was passed through to the "visible units" list. But that looks like VERY unoptimal algorithm.

    Otherwise, I could shoot a ray to every enemy unit in a frontal cone in a max view distance, and then check if ray hits an obstacle, and how many vision imparing hazards are in the way (smoke). Again, when having many units, that probably would be WAY too expensive. And then again we need to somehow check what terrain do we see.
     
  4. kkmic

    kkmic Undefined

    Joined:
    Jul 17, 2014
    Messages:
    59
    Likes Received:
    2
    Yeah, the turn-based mechanic simplified the "when" in vision checks for OXC.

    Still, doing vision checks all the time will result in lots of wasted cycles, so I tend to incline to an event-based solution (similar to OXC).

    Basically, you keep a list of tiles in the agent's view, and then when an event happens, first check if it's in one of those tiles and then:
    • if it's a "terrain" event (destroyed tiles, falling floors?, doors that open/close, agents change tile/position) then update the tiles list and visible entities in the tiles
    • If it's a "entity" event (aliens change tile, weapon bolt changes tile, smoke generated/dispersed), then change the visibility of affected entities accordingly (for example, taking smoke/gas into account)

    Easier said than done, I know, but by going with the periodic visibility checks you both waste processor power and provide a sub-optimal experience (just like the original Apoc did).
     
  5. Solarius Scorch

    Solarius Scorch Call to Power modder Global Moderator

    Joined:
    Jul 8, 2014
    Messages:
    194
    Likes Received:
    9
    You might want to look at the OXCE+ version of Openxcom, as it modified vision checks, gaining huge FPS improvement with preserved functionality. I don't know the code though, or if it'll be useful.
     
  6. JonnyH

    JonnyH Well-Known Member Official Developer Administrator

    Joined:
    Jul 17, 2014
    Messages:
    183
    Likes Received:
    45
    I kinda dislike the non-determinism of 'time based' sight checks, they're not directly related to user input, so feels like you're not really in control. Better to have something that directly triggered by ingame actions - like a unit entering a new tile, or all units within $RADIUS to be updated when a scenery tile is removed (not sure how this would interact with falling tiles though, are they instantly considered 'see through' when they start falling?).

    As for the vision checks being expensive, there's lots I can do to make the voxel map checks faster in the future, so there's plenty in the bag if it's a 'bit slow' now it should be relatively easily improved.
     
  7. Istrebitel

    Istrebitel Well-Known Member Official Developer Administrator

    Joined:
    Aug 8, 2016
    Messages:
    185
    Likes Received:
    83
    Well, I tried multiple improvements and speed was still not optimal. And then I ran Apoc and it dawned to me that Apoc never worked the same as UFO 1&2 do! Lol. I forgot about it completely.

    Apoc does not vision check to individual tiles, Apoc does checks to LOS blocks only. You can either see whole block or nothing. That is obviously way faster. We just try to "see" into every block (collision check using LOS loftemps) that is in our field of vision and view distance. If we can reach inside, we see whole block.

    Units, then, we check individually. Again, we collison check to every unit within our FOV and distance.

    Implementing it that way made algorithm WAY faster and now it's working fine.
     
  8. Istrebitel

    Istrebitel Well-Known Member Official Developer Administrator

    Joined:
    Aug 8, 2016
    Messages:
    185
    Likes Received:
    83
    Anway, LOS calculations are working fine now, and we have a much bigger problem that needs solving.

    Pathfinding in battlescape

    After I have implemented patrol AI, I confirmed my suspicions, that unfortunately, our A* does not suffice in the battlescape's complex landscape. Simpliest example is that on every UFO map, aliens trying to exit UFO to patrol have to make a detour to leave through the door, which is often a big detour in terms of comparing path length to a direct line. This makes A* try all sorts of ways of bumping into UFO walls, looking for a faster way which is not there.

    Therefore, we must change our pathfinding algorithm for battlescape. Original game used LOS blocks and I assume we should do. I have an idea, but I'd like to hear if someone else has a better one.

    I think we should create a weighted graph, four graphs in fact, for walker/flyer and small/large units. The graph's verticles are LOS blocks, while edges are showing which blocks are connected to each other. Edge's weight is amount of tiles it takes to travel from block to block.

    When pathfinding, we should first pathfind using A* on the relevant graph (based on unit type). After we know which LOS blocks we have to go through, we go through these blocks, pick a position in every LOS block that is closest to goal and path to it using current algorithm. This way we end up with a somewhat optimal path to destination
     
  9. Istrebitel

    Istrebitel Well-Known Member Official Developer Administrator

    Joined:
    Aug 8, 2016
    Messages:
    185
    Likes Received:
    83
    I guess at least someone's going to be glad to know that I've managed to implement an improved pathfinding algorithm for battlescape, one that uses LOS blocks to navigate greater distances. It needs further improvement of course, but right now it's at least correctly working and working very fast too (no noticeable slowdowns no matter the distance). Here's a small demo (audio is shit, soz):

    https://www.youtube.com/watch?v=l_z6YfNbvyU&feature=youtu.be
     
  10. pmprog

    pmprog Well-Known Member Official Developer Administrator

    Joined:
    Jul 8, 2014
    Messages:
    175
    Likes Received:
    12
    Damn Istrebitel... I'm well behind in your battlescape work. Need to try and find some time and read most of this thread!
     
  11. Istrebitel

    Istrebitel Well-Known Member Official Developer Administrator

    Joined:
    Aug 8, 2016
    Messages:
    185
    Likes Received:
    83
    I'm well behind myself. Haven't done anything for like 2 months or so. Well, gotta come back soon.
     
  12. Istrebitel

    Istrebitel Well-Known Member Official Developer Administrator

    Joined:
    Aug 8, 2016
    Messages:
    185
    Likes Received:
    83
    I hope JonnyH is still around because I found a bug I'm not sure I'll be able to fix.

    Basically, the way our save-load works, is that it doesn't look if sp to a class has a derived class. For example, I'm having an AI stack of sorts for units, each unit has several AIs attached to it (panic, default, behavior, vanilla alien ai etc.). It's a std::list<sp<AI>> but the classes that are stored there are derived - VanillaAI, PanicAI etc. When I save-load, class AI gets saved and loaded, and that means it stops working.

    We need our save-load system to recognize what derived class is actually stored in a pointer to a base class, and save/load accordingly.
     
  13. JonnyH

    JonnyH Well-Known Member Official Developer Administrator

    Joined:
    Jul 17, 2014
    Messages:
    183
    Likes Received:
    45
    Yeah, currently any selection for 'which' serialization code to use is purely done at build time, which means you can't store sp<Superclass> in the GameState, as it'll run the Superclass serialization not the Subclass. I don't know how we could solve this, I kinda bypassed it for some things by having the 'parent' class store everything, and instead switch behaviours based on an enum rather than a subclassed type (see: VehicleMission stuff), though that's something of a mess (lots of big switch statements) so it may be an opportunity to improve.
     
  14. Istrebitel

    Istrebitel Well-Known Member Official Developer Administrator

    Joined:
    Aug 8, 2016
    Messages:
    185
    Likes Received:
    83
    So one option would be to make custom save/load for superclass, that checks which subclass is stored in pointer/xml, and saves/loads accordingly using subclass's serializer?
     
  15. Istrebitel

    Istrebitel Well-Known Member Official Developer Administrator

    Joined:
    Aug 8, 2016
    Messages:
    185
    Likes Received:
    83
    Okay, I did it. I had to modify the generator for serialize to include a new "full" option that serializes out regardless of equality with default (because it must save "type" value even if it matches the default thing, otherwise I have no way of knowing which type to read). I just removed the if clause, but maybe there's more to be done (like, removing comparing operators as they're no longer needed?)

    Also, I need to have external enums. Right now I just declare them in the gamestate_serialize.h but I'd rather mark them "external', but for some reason in code it says "there are no external enums". Maybe it could be changed? I dunno, didn't want to break it so didn't touch it, but maybe that could be changed to allow external enums? Then I wouldn't have to manually add declarations for AI types.

    It is now in my git and I will push to pmprog after Travis confirms it's ok.
     
  16. Istrebitel

    Istrebitel Well-Known Member Official Developer Administrator

    Joined:
    Aug 8, 2016
    Messages:
    185
    Likes Received:
    83
    Hmm, test fails saying gamestate differs when saving after entering battle. How can I found out what is actually different? I doesn't seem like tests are included in Visual Studio project
     
  17. Istrebitel

    Istrebitel Well-Known Member Official Developer Administrator

    Joined:
    Aug 8, 2016
    Messages:
    185
    Likes Received:
    83
    I tried to compile this test under VS but I get a weird problem. As soon as I introduce "using namespace Openapoc" into the cpp file, I get this error:

    Error C2678 binary '==': no operator found which takes a left - hand operand of type 'Concurrency::details::_Task_impl<_ReturnType>'
    (or there is no acceptable conversion) test_serialize E : \Projects\GitHub\OpenApoc\game\state\gamestate_serialize.h 106
     
  18. JonnyH

    JonnyH Well-Known Member Official Developer Administrator

    Joined:
    Jul 17, 2014
    Messages:
    183
    Likes Received:
    45
    What test_serialize does is create a Gamestate, save it to disk, read in that saved version and checks (using operator==) the gamestate we used to save and the gamestate loaded in from the disk are exactly the same.

    It may be that you need to implement an operator== for any classes you had to write 'custom' serialization for too.

    One issue I've noticed with this is it doesn't deal with re-ordering, such as storing a set of sp<> objects (or a map where an sp<> is the key) may be a 'random' order, as the pointers returned by allocation are not guaranteed to be sorted in any way.

    It may be possible to make operator== a bit more clever for these types, but that would also be more expensive...
     
  19. Istrebitel

    Istrebitel Well-Known Member Official Developer Administrator

    Joined:
    Aug 8, 2016
    Messages:
    185
    Likes Received:
    83
    New question:

    I am absolutely sure I remember this never happened before, but maybe it did? When I click in pause button, if it's already pressed, it becomes depressed, but nothing happens. Result is no button is pressed at all. I believe it should not become depressed because it's a radiobutton and at least one must always be pressed?
     
  20. Istrebitel

    Istrebitel Well-Known Member Official Developer Administrator

    Joined:
    Aug 8, 2016
    Messages:
    185
    Likes Received:
    83
    And another new thing:

    I implemented fire sound and something was off. Sound was wrong. I checked and apparently, the game plays fire sound at half sample rate! Meaning, even though file is marked 22050, it's played in 11025. That can be confirmed by recording game sound (I used Audacity) while fire is burning on screen, and then comparing it to a twice stretched fire sound (zextra/burning). They will be identical.

    When trying to enter a 11025 samplerate I encountered an error in sdldraw_backend.cpp::72 - Access Violation. Reading documentation, cvt.buf's length should be cvt.len * cvt.mul. However, buf's length is 4 times cvt.len but cvt.mul is 8. I believe the problem is that frequency was not factored when calculating. I fixed it but check it just in case, because I might have broken something else.

    And also, we must figure out wether some other sounds should also play in different samplerate? Why did we assume sounds are in 22050 format?
     

Share This Page