Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove scent diffusion over water by implementing NO_SCENT flag #37129

Merged
merged 4 commits into from
Apr 2, 2020

Conversation

sayke
Copy link
Contributor

@sayke sayke commented Jan 16, 2020

Summary

SUMMARY: Bugfixes "Removes scent diffusion over water by implementing NO_SCENT flag"

Purpose of change

Scent trails shouldn't persist over water, and zombies/hordes shouldn't easily follow scent trails over water, such that survivors taking a boat or swimming across water shouldn't be easily tracked by scent. With this change, zombies/hordes will be less likely to follow survivors across bodies of water.

This has other interesting tactical implications as well: It means survivors (especially survivors with Strong Scent) can benefit by ducking in and out of rivers and lakes, jumping in a canoe to avoid being tracked by smell, building a base in relative safety across a river, hiding in the deep end, etc.

Describe the solution

  • Created new NO_SCENT (json) and TFLAG_NO_SCENT (in-memory) flags
  • Updated the scent-diffusion code to use the NO_SCENT flag instead of the WALL flag (the WALL flag no longer affects scent diffusion)
  • Left the map-generation and tile-linking code that uses the WALL flag alone (the WALL flag affects map generation and drawing the same way it did before)
  • Updated the terrain-types json definitions that previously used WALL to also use NO_SCENT
  • Fixed a few tile definitions that seemed like they should have NO_SCENT, REDUCE_SCENT, or be PERMEABLE, but previously weren't
  • Updated documentation

Describe alternatives you've considered

I originally just added REDUCE_SCENT to water tiles in the json but that just resulted in extremely concentrated scent trails when moving across water, rather than reducing the total scent trail left behind, like so:
Screen Shot 2020-01-16 at 12 27 21 AM

That didn't work, but adding WALL did, so as per suggestions I implemented new NO_SCENT and TFLAG_NO_SCENT flags and updated the code to use them.

I think removing scent from water makes sense in most cases: For example, there shouldn't be the same kind of scent trails from sweat or chemical traces left on water, because water doesn't have the kind of surface that human sweat etc can stick to. This effectively makes water tiles into wall tiles for scent-diffusion purposes.

However, I don't think this solution makes sense all cases: For example, smoke should ideally be able to waft some distance over water, even if a bloodhound can only follow a trail up to the edge of the water and sniff around there... This code doesn't support that, but I have no idea what a more complex scent diffusion system should look like, and in any case this slightly extends existing functionality to create interesting and realistic tactical options for creative survivors.

Testing

  1. Teleport to a body of water
  2. Spawn a kayak
  3. Kayak out into the water
  4. Use the debug scent-mapping tool to look at the scent trail

Additional context

Scent trail over water before this change:
Screen Shot 2020-01-16 at 12 23 06 AM

Scent trail over water after this change:
Screen Shot 2020-01-17 at 10 52 07 AM

Adding REDUCE_SCENT tag to liquid tiles in order to make sure water reduces scent trails
@kevingranade
Copy link
Member

REDUCE_SCENT is working as intended, but arguably has a bad name.
It means exactly what you realized, which is "reduce diffusion of scent", this is intended for use with obstacles to scent that still allow some permeability, so e.g. a door allows scent to diffuse through cracks as opposed to a wall that blocks it entirely.

@sayke
Copy link
Contributor Author

sayke commented Jan 17, 2020

Aha! What do you think about me adding a new flag (say, DISSIPATES_SCENT) intended to reduce scent trails over water?

@kevingranade
Copy link
Member

I'm ok with that in principle, but this is an incredibly complicated and fragile piece of code, so good luck.

@sayke
Copy link
Contributor Author

sayke commented Jan 17, 2020

Ok... Trying a new tack and found something weird maybe: TFLAG_WALL is supposed to block scent but it doesn't seem to. In scent_map.cpp there's a comment that says: "currently only TFLAG_WALL blocks scent", and from the code it looks like it should in fact do that. However, when I put TFLAG_WALL instead of REDUCES_SCENT into the water terrain tiles in terrain-liquids.json... It doesn't seem to do anything. Scent seems to be propagating over water without being blocked by TFLAG_WALL.

Screen Shot 2020-01-17 at 1 55 24 AM

From a quick search of the code, it seems to me like TFLAG_WALL is only really (intended to be?) used for two things:

  1. Blocking scent propagation (it sets in scent_map::update() in scent_map.cpp)
    // The new scent flag searching function. Should be wayyy faster than the old one.
    m.scent_blockers( blocks_scent, reduces_scent, point( scentmap_minx - 1, scentmap_miny - 1 ),
                      point( scentmap_maxx + 1, scentmap_maxy + 1 ) );
    // Sum neighbors in the y direction.  This way, each square gets called 3 times instead of 9
    // times. This cost us an extra loop here, but it also eliminated a loop at the end, so there
    // is a net performance improvement over the old code. Could probably still be better.
    // note: this method needs an array that is one square larger on each side in the x direction
    // than the final scent matrix. I think this is fine since SCENT_RADIUS is less than
    // MAPSIZE_X, but if that changes, this may need tweaking.
    for( int x = scentmap_minx - 1; x <= scentmap_maxx + 1; ++x ) {
        for( int y = scentmap_miny; y <= scentmap_maxy; ++y ) {
            // remember the sum of the scent val for the 3 neighboring squares that can defuse into
            sum_3_scent_y[y][x] = 0;
            squares_used_y[y][x] = 0;
            for( int i = y - 1; i <= y + 1; ++i ) {
                if( !blocks_scent[x][i] ) {
                    if( reduces_scent[x][i] ) {
                        // only 20% of scent can diffuse on REDUCE_SCENT squares
                        sum_3_scent_y[y][x] += 2 * grscent[x][i];
                        squares_used_y[y][x] += 2;
                    } else {
                        sum_3_scent_y[y][x] += 10 * grscent[x][i];
                        squares_used_y[y][x] += 10;
                    }
                }
            }
        }
    }
  1. Defining wall connections during mapgen (first in map::scent_blockers() in map.cpp):
void map::scent_blockers( std::array<std::array<bool, MAPSIZE_X>, MAPSIZE_Y> &blocks_scent,
                          std::array<std::array<bool, MAPSIZE_X>, MAPSIZE_Y> &reduces_scent,
                          const point &min, const point &max )
{
    auto reduce = TFLAG_REDUCE_SCENT;
    auto block = TFLAG_WALL;
    auto fill_values = [&]( const tripoint & gp, const submap * sm, const point & lp ) {
        // We need to generate the x/y coordinates, because we can't get them "for free"
        const int x = gp.x * SEEX + lp.x;
        const int y = gp.y * SEEY + lp.y;
        if( sm->get_ter( lp ).obj().has_flag( block ) ) {
            blocks_scent[x][y] = true;
            reduces_scent[x][y] = false;
        } else if( sm->get_ter( lp ).obj().has_flag( reduce ) ||
                   sm->get_furn( lp ).obj().has_flag( reduce ) ) {
            blocks_scent[x][y] = false;
            reduces_scent[x][y] = true;
        } else {
            blocks_scent[x][y] = false;
            reduces_scent[x][y] = false;
        }

        return ITER_CONTINUE;
    };

(...then in map_data_common_t::set_flag() in mapdata.cpp):

void map_data_common_t::set_flag( const std::string &flag )
{
    flags.insert( flag );
    const auto it = ter_bitflags_map.find( flag );
    if( it != ter_bitflags_map.end() ) {
        bitflags.set( it->second );
        if( !transparent && it->second == TFLAG_TRANSPARENT ) {
            transparent = true;
        }
        // wall connection check for JSON backwards compatibility
        if( it->second == TFLAG_WALL || it->second == TFLAG_CONNECT_TO_WALL ) {
            set_connects( "WALL" );
        }
    }
}

So, in theory, if TFLAG_WALL actually did zero out scent, then adding it to the liquid terrains should basically do the trick: Scent wouldn't exist on water tiles, so crossing water would stop monsters from tracking you by scent.

...However, TFLAG_WALL doesn't seem to be affecting scent at all on water tiles! At least when I added it to water tiles it didn't seem to... I think I'm testing this right. Can you reproduce?

If necessary, I think I could split TFLAG_WALL into two flags - NO_SCENT and TFLAG_WALL, using NO_SCENT for the few parts of the code that currently use TFLAG_WALL for blocking scent propagation, and leaving TFLAG_WALL for the rest of the code (basically just in defining mapgen wall connections).

What am I missing? Am I thinking about this right?

@anothersimulacrum
Copy link
Member

Did you add "TFLAG_WALL" or "WALL" to the flags? Because adding "WALL" works for me.
image

Those flags are converted to TFLAG_s when loaded, so to find what they are in JSON, look at this

{ "WALL", TFLAG_WALL }, // smells

Update from using REDUCE_SCENT tag to WALL tag. WALL eliminates scent over water completely, which makes sense in most cases (like there being no scent trail from sweat or chemical traces left on surfaces) but not all (smoke should be able to waft some distance over water)...

This still has interesting tactical implications: It means suvivors (especially suvivors with Strong Scent) can benefit by ducking in and out of rivers and lakes, jumping in a canoe to avoid being tracked by smell, etc.
@sayke
Copy link
Contributor Author

sayke commented Jan 22, 2020

Thank you for that, @anothersimulacrum! I changed it from REDUCE _SCENT to WALL and it works great now. WALL eliminates scent over water completely, which I think makes sense in most cases (like there being no scent trail from sweat or chemical traces left on surfaces) but not all (smoke should be able to waft some distance over water)...

This still has interesting tactical implications: It means survivors (especially survivors with Strong Scent) can benefit by ducking in and out of rivers and lakes, jumping in a canoe to avoid being tracked by smell, hiding in the swimming pool, etc.

It's a bit messy, because obviously water tiles aren't walls, and I'm not sure how it could interact with map-generation code (in scent_map::update() in scent_map.cpp and in map::scent_blockers() in map.cpp)... But I just generated a world and tested it out and it seems to work.
image

@anothersimulacrum
Copy link
Member

You should implement a new flag that has the same effect on scent as the WALL flag, instead of using the WALL flag.

@sayke
Copy link
Contributor Author

sayke commented Jan 22, 2020

My c++ is totally rusty but I'm up for that. Let me see if I can try to figure it out...

* Created new NO_SCENT (json) and TFLAG_NO_SCENT (in-memory) flags
* Updated the scent-diffusion code to use the NO_SCENT flag instead of the WALL flag (the WALL flag no longer affects scent diffusion)
* Left the map-generation and tile-linking code that uses the WALL flag alone (the WALL flag affects map generation and drawing the same way it did before)
* Updated the terrain-types json definitions that previously used WALL to also use NO_SCENT
* Fixed a few tile defintions that seemed like they should have NO_SCENT, REDUCE_SCENT, or be PERMIABLE, but previously weren't
* Updated documentation
@sayke sayke changed the title Reduce size of scent trail over water by adding REDUCE_SCENT tag Remove scent diffusion over water by implementing NO_SCENT flag Jan 22, 2020
…sion across things besides WALLs"

Fixing summary
@sayke
Copy link
Contributor Author

sayke commented Jan 22, 2020

Ok! This PR now:

  • Creates a new NO_SCENT (json) and TFLAG_NO_SCENT (in-memory) flags
  • Updates the scent-diffusion code to use the NO_SCENT flag instead of the WALL flag (the WALL flag no longer affects scent diffusion)
  • Leaves the map-generation and tile-linking code that uses the WALL flag alone (the WALL flag affects map generation and drawing the same way it did before)
  • Updates the terrain-types json definitions that previously used WALL to also use NO_SCENT
  • Fixes a few tile definitions that seemed like they should have NO_SCENT, REDUCE_SCENT, or be PERMEABLE, but previously weren't
  • Updates documentation

@Kelenius
Copy link
Contributor

@sayke
Copy link
Contributor Author

sayke commented Jan 22, 2020

In the Mythbusters episode the bloodhounds didn't track Jamie in the water. They tracked him on the shore by the water, where he got in and where he got out. Bloodhounds can't track you in the water proper. If Jamie had swam out into the lake and the bloodhound couldn't see him, it would only know where he got into the water - it wouldn't be able to track him in the water:

"If you cross a body of water and a tracking dog and its handler believes you’ve entered the water, they will either have to cross the water also, or circle around to the other side where they will likely pick up your scent again where you exited the water as your scent drips off you and along the ground." (from here)

As noted at the top here, this change isn't perfect but it approximates that behavior.

@Kelenius
Copy link
Contributor

Okay. Mythbusters is probably not the most reliable source for this, but based on my understanding, the scent goes through the air, not the ground, and going into water doesn't stop it from spreading, and some quick searches (again, not the most reliable source, but still) seem to agree that dogs can track people through water just fine. I'm not an expect on this, however, but I think that adding this to do game would need some sources that would show that it actually works.

@sayke
Copy link
Contributor Author

sayke commented Jan 22, 2020

Dogs don't track scent through water. If you get in a kayak on the shore and then paddle off into the sea, the best bloodhounds in the world aren't going to be able to follow your path over the ocean.

As noted at the top here, this change isn't perfect but it improves on previous functionality and approximates ideal behavior.

@sayke
Copy link
Contributor Author

sayke commented Jan 22, 2020

Also, I note that a build test appears to fail for reasons unrelated to this PR:

Test world "Tom" left for inspection.
Ended test at Wed Jan 22 08:17:18 2020
The test took 90.468 seconds
08:15:40.662 WARNING : opendir [./save/Tom/mods] failed with "No such file or directory".make[1]: *** [check] Error 1
Makefile:39: recipe for target 'check' failed
make[1]: Leaving directory '/home/runner/work/Cataclysm-DDA/Cataclysm-DDA/tests'
make: *** [check] Error 2
Makefile:1082: recipe for target 'check' failed
##[error]Process completed with exit code 2.

I updated the summary line too, which should (I think?) fix the PR Validator block.

@sayke
Copy link
Contributor Author

sayke commented Jan 23, 2020

Update: That did not fix the summary line so the PR Validator tests still fail. How do I fix it?

@Kelenius
Copy link
Contributor

Kelenius commented Jan 23, 2020

Dogs don't track scent through water. If you get in a kayak on the shore and then paddle off into the sea, the best bloodhounds in the world aren't going to be able to follow your path over the ocean.

Your own source says:


What About Crossing A Lake, Pond, Or River?

Unfortunately, all water is said to do is carry your scent back to the ground as water drips off you, making it even easier for a dog to pick up your scent (so say the experts).

If you cross a body of water and a tracking dog and its handler believes you’ve entered the water, they will either have to cross the water also, or circle around to the other side where they will likely pick up your scent again where you exited the water as your scent drips off you and along the ground.

Crossing bodies of water is a trick to lose human trackers, who rely mostly on visuals. It breaks off your track because you won’t leave footprints or other telltale signs of human activity in a body of water. But this doesn’t work on dogs, who rely primarily on scent.


Obviously if you kayak into the sea, you're not going to be followed by dogs. But not because of the scent. It's because most dogs don't have kayaks, and even fewer can navigate oceans. Zombies aren't dogs, they're not concerned with breathing, and will blindly follow your scent into the water, kayak or no. So far there have been three sources in this tread that say the scent is not broken by the water, do you have any reliable sources that indicate otherwise?

@sayke
Copy link
Contributor Author

sayke commented Jan 23, 2020

That's already been addressed. See where it says "all water is said to do is carry your scent back to the ground as water drips off you"? Air scent is only detectable downwind and dissipates very quickly (in a matter of minutes at most), which is why the scent trail ("track") that bloodhounds follow is mostly on the ground. Ground scent doesn't exist on the water, in the water, or over the water.

This makes sense, because (according to the CIA) air scent is mostly derived from sebum, while (according to books cited by Wikipedia) ground scent is mostly derived from trampled vegetation, bugs, mud, and soil disturbed by an individuals footprints. Sebum can be left on the ground and on objects touched, but can only be detected from distance over the air when immediately downwind. Water that drips off you will thus leave a strong ground scent trail, but ground scent trails don't exist on the water, so that only matters when you reach shore.

As Karthas077 pointed out in the Discord: "This is why when tracking someone with dogs, you want at least four dogs. Come to a river? Two split up to search up and down the current bank, two cross to search up and down the opposite bank. Regroup once scent is found again."

As noted at the top here, this change isn't perfect but it improves on previous functionality, creates interesting tactical options for the player, and approximates ideal behavior: Ground scent trails don't exist on the water.

@I-am-Erk I-am-Erk added [C++] Changes (can be) made in C++. Previously named `Code` [JSON] Changes (can be) made in JSON Fields / Furniture / Terrain / Traps Objects that are part of the map or its features. labels Jan 24, 2020
@I-am-Erk
Copy link
Member

I-am-Erk commented Jan 24, 2020

Zombies aren't bloodhounds anyway. If dogs have increased difficulty tracking scents over water, zombies would be much worse at it.

Keeping this as a different flag is a good idea, because going forward we'd probably want it to reduce but not eliminate scent. In the meantime this is a good solution I think. I'm not going to merge it because it has C++ changes so I shouldn't be trusted with it, but I have no problem with adding it.

@kevingranade kevingranade merged commit 8f67cad into CleverRaven:master Apr 2, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[C++] Changes (can be) made in C++. Previously named `Code` Fields / Furniture / Terrain / Traps Objects that are part of the map or its features. [JSON] Changes (can be) made in JSON
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants