Windows catacurse animations (Issue #750)

From the issue tracker:
https://github.com/TheDarklingWolf/Cataclysm-DDA/pull/750

I get the feeling that LazyCat doesn't understand why frequent full-window repaints are a Bad Thing.

I don’t understand? Hah! Go on then, tell us what is it you were trying to say, what is bad, how is it bad, and what’s better?

Maybe we can fix the flicker by figuring out how to specify an update region that isn't the entire window? It looks like the third argument can be used to specify a set of rectangles to update, maybe we should look into using that.

No, that is irrelevant to flickering and is already handled in the code. The only purpose of it is to optimise back buffer drawings, but screen blit or double buffering flip updates whole screen and there is nothing you can do about it. That’s how video cards work, all you have there is option to wait or not wait for vertical refresh.

However you problem is even more trivial, and as I explained in another thread you simply need to look into those menus which are flickering. Crafting and options menu are the only ones I could find that are flickering with my original bugfix proposal, but if there is something else that doesn’t work simply tell me about it and I’ll fix it. Just ask, there is no reason to guess or assume.

1.) catacurse.cpp (enable animations & optimise)

void DrawWindow(WINDOW *win)
{
.
.

    win->draw=false;                //We drew the window, mark it as so

//CAT:
    RedrawWindow(WindowHandle, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
};
int getch(void)
{
 // standards note: getch is sometimes required to call refresh
 // see, e.g., http://linux.die.net/man/3/getch
 // so although it's non-obvious, that refresh() call (and maybe InvalidateRect?) IS supposed to be there

//CAT:
// refresh();
// InvalidateRect(WindowHandle,NULL,true);
.
.

//CAT:
//            Sleep(2);
        }
        while (endtime<(starttime+inputdelay));
    }
    else
    {
        CheckMessages();
    };

//CAT:
//    Sleep(25);
    return lastchar;
};
int werase(WINDOW *win)
{
.
.

//CAT:
//    wrefresh(win);
    return 1;
};

2.) map.cpp (lets make driving not be slower than walking)

bool map::vehproceed(game* g)
{
.
.

//CAT:
/*
   if (pl_ctrl && veh->velocity) {
      // a bit of delay for animation
      // total delay is roughly one third of a second.
      int ns_per_frame = abs ( (BILLION/3) / ( (float)veh->velocity / 1000) );
      if (ns_per_frame > BILLION/15)
         ns_per_frame = BILLION/15;
      timespec ts;   // Timespec for the animation
      ts.tv_sec = 0;
      ts.tv_nsec = ns_per_frame;
      nanosleep (&ts, 0);
   }
*/

3.) options. cpp (remove flickering in options menu)

void game::show_options()
{
 WINDOW* w_options_border = newwin(25, 80, (TERMY > 25) ? (TERMY-25)/2 : 0, (TERMX > 80) ? (TERMX-80)/2 : 0);
 WINDOW* w_options = newwin(23, 78, 1 + ((TERMY > 25) ? (TERMY-25)/2 : 0), 1 + ((TERMX > 80) ? (TERMX-80)/2 : 0));


//CAT: move here from below
  wborder(w_options_border, LINE_XOXO, LINE_XOXO, LINE_OXOX, LINE_OXOX,
                            LINE_OXXO, LINE_OOXX, LINE_XXOO, LINE_XOOX);
  mvwprintz(w_options_border, 0, 36, c_ltred, " OPTIONS ");
  wrefresh(w_options_border);
//CAT: *** ^^^


 int offset = 1;
 const int MAX_LINE = 22;
 int line = 0;
 char ch = ' ';
 bool changed_options = false;
 bool needs_refresh = true;
 do {


//CAT: move above, outside do-loop
/*
  wborder(w_options_border, LINE_XOXO, LINE_XOXO, LINE_OXOX, LINE_OXOX,
                            LINE_OXXO, LINE_OOXX, LINE_XXOO, LINE_XOOX);
  mvwprintz(w_options_border, 0, 36, c_ltred, " OPTIONS ");
  wrefresh(w_options_border);
*/
//CAT: *** ^^^
.
.

  wrefresh(w_options);

//CAT:
//  refresh();
  ch = input();
  needs_refresh = true;

//CAT:
//  refresh();

 switch (ch) {
// move up and down
.
.

3.) crafting.cpp (remove flickering in crafting menu)

recipe* game::select_crafting_recipe()
{
.
.

        mvwputch(w_data, 21,  0, c_ltgray, LINE_XXOO); // _|
        mvwputch(w_data, 21, 79, c_ltgray, LINE_XOOX); // |_

//CAT:
//        wrefresh(w_data);

        int recmin = 0, recmax = current.size();
        if(recmax > MAX_DISPLAYED_RECIPES)
        {
.
.

I didn’t realize how much catacurse was being used in this new build. Maybe I should have stuck a little license header in the top of it to separate it out from Whale’s stuff :3
I wrote this library from scratch so if there were any questions about why stuff is the way it is, I could maybe answer some stuff. And my name mentioned somewhere wouldn’t hurt >.>

[quote=“Teseng, post:2, topic:1171”]I didn’t realize how much catacurse was being used in this new build. Maybe I should have stuck a little license header in the top of it to separate it out from Whale’s stuff :3
I wrote this library from scratch so if there were any questions about why stuff is the way it is, I could maybe answer some stuff. And my name mentioned somewhere wouldn’t hurt >.>[/quote]

I don’t see what would be the purpose of license, but surely they should have mentioned you in the credits.

Were animations working when you wrote it? What for are calls to sleep() in getch() function?

Just gave it a try. Runs fine on my Win7 machine. Couldn't find flickering anywhere.

Only found 2 minor things, but I can’t tell if this is Windows specific:

  • Driving a car in the rain stops the rain animation while the car is moving. Looks kind of silly if you keep your 5 button pressed.
  • Thrown things are not animated, but shots are.

Move rain animation code from handle_action() to draw() or draw_ter() where it belongs, or perhaps in weather.cpp to be handled together with rain puddles. Throwing things is not supposed to be animated, that is there isn’t any code to handled it, so it’s the same on Linux. There are several other issues, like debug messages could be invisible or blanking the screen and some of “debug menu” options are not refreshing properly in a similar manner, that is they are most likely refreshing where they shouldn’t like it was the case with options and crafting menu.

The rain. It’s a hack to interlace drawing functions with input functions and vice versa, like drawing in input function in catacurese.cpp and like handling input in drawing function for the rain effect. So if you move the rain drawing code together with the rest of the terrain drawing code as I suggest you will notice the rain doesn’t move when you don’t move anymore, as it should be. The whole game is coded like that, the time stands still when you stand still and nothing moves, so why should rain. And if you insist it should move it will be using up all the available CPU by constantly looping through itself like it does now. Anyway, here is what I did:

bool game::handle_action()
{

//CAT:
  char ch = input();

  if (keymap.find(ch) == keymap.end()) {
	  if (ch != ' ' && ch != '\n')
		  add_msg("Unknown command: '%c'", ch);
	  return false;
  }

 action_id act = keymap[ch];
.
.
void game::draw_ter(int posx, int posy)
{
.
.

//CAT: *** vvv
 if(levz >= 0)
 {
	  float fFactor = 0.01;
	  char cGlyph = ',';
	  nc_color colGlyph = c_ltblue;
	  bool bWeatherEffect = true;
	  switch(weather) 
	  {
      	  case WEATHER_ACID_DRIZZLE:
	            cGlyph = ',';
            	colGlyph = c_ltgreen;
      	      fFactor = 0.01;
	            break;
      	  case WEATHER_ACID_RAIN:
	            cGlyph = ',';
	            colGlyph = c_ltgreen;
	            fFactor = 0.03;
      	      break;
	        case WEATHER_DRIZZLE:
      	      cGlyph = ',';
	            colGlyph = c_ltblue;
      	      fFactor = 0.01;
	            break;
      	  case WEATHER_RAINY:
	            cGlyph = ',';
      	      colGlyph = c_ltblue;
	            fFactor = 0.04;
      	      break;
	        case WEATHER_THUNDER:
	            cGlyph = ',';
      	      colGlyph = c_ltblue;
	            fFactor = 0.05;
      	      break;
	        case WEATHER_LIGHTNING:
      	      cGlyph = ',';
	            colGlyph = c_ltblue;
      	      fFactor = 0.03;
	            break;
//CAT-mgs:
      	  case WEATHER_FLURRIES:
	            cGlyph = '.';
      	      colGlyph = c_white;
	            fFactor = 0.01;
      	      break;
      	  case WEATHER_SNOW:
	            cGlyph = '*';
      	      colGlyph = c_white;
	            fFactor = 0.03;
      	      break;
	        case WEATHER_SNOWSTORM:
      	      cGlyph = '*';
	            colGlyph = c_white;
            	fFactor = 0.05;
      	      break;
	        default:
            	bWeatherEffect = false;
      	      break;
	  }

//CAT: *** vvv
	  if(bWeatherEffect)
	  {
		int iStartX = (TERRAIN_WINDOW_WIDTH > 121) ? (TERRAIN_WINDOW_WIDTH-121)/2 : 0;
		int iStartY = (TERRAIN_WINDOW_HEIGHT > 121) ? (TERRAIN_WINDOW_HEIGHT-121)/2: 0;
		int iEndX = (TERRAIN_WINDOW_WIDTH > 121) ? TERRAIN_WINDOW_WIDTH-(TERRAIN_WINDOW_WIDTH-121)/2: TERRAIN_WINDOW_WIDTH;
		int iEndY = (TERRAIN_WINDOW_HEIGHT > 121) ? TERRAIN_WINDOW_HEIGHT-(TERRAIN_WINDOW_HEIGHT-121)/2: TERRAIN_WINDOW_HEIGHT;


		int dropCount = iEndX * iEndY * fFactor;
		for(int i=0; i < dropCount; i++)
		{
			int iRandX = rng(iStartX, iEndX-1);
			int iRandY = rng(iStartY, iEndY-1);

			if( m.is_outside(u.posx+(iRandX-SEEX), u.posy+(iRandY-SEEY))
					&& (iRandX!=SEEX && iRandY!=SEEY) )
				mvwputch(w_terrain, iRandY, iRandX, colGlyph, cGlyph);
		}
	  }
 } 


 if (u.has_disease(DI_VISUALS) || (u.has_disease(DI_HOT_HEAD) && u.disease_intensity(DI_HOT_HEAD) != 1))
   hallucinate(posx, posy);

 wrefresh(w_terrain);
}

When I mean license, I don’t mean to keep you guys from using it or anything. People liked it so I was going to release it by itself, for other developers to use in other roguelikes, so it needs a simple license attached to it. Nothing major, it will be released under the Zlib license, so there’s no problem there. The Sleep() just releases the CPU for a split second, and keeps it from hogging the CPU. It will usually even cap out the core at 100% usage. It used to depend on a call to MsgWaitForMultipleObjects which makes the thread sleep until it detects a button press, but that probably won’t work with animations, as it only updates when getch is called (a key is pressed).

I have several unreleased versions, including one that uses an actual windows console window, and a half-written one that is in DirectX.

[quote=“Teseng, post:5, topic:1171”]When I mean license, I don’t mean to keep you guys from using it or anything. People liked it so I was going to release it by itself, for other developers to use in other roguelikes, so it needs a simple license attached to it. Nothing major, it will be released under the Zlib license, so there’s no problem there. The Sleep() just releases the CPU for a split second, and keeps it from hogging the CPU. It will usually even cap out the core at 100% usage. It used to depend on a call to MsgWaitForMultipleObjects which makes the thread sleep until it detects a button press, but that probably won’t work with animations, as it only updates when getch is called (a key is pressed).

I have several unreleased versions, including one that uses an actual windows console window, and a half-written one that is in DirectX.[/quote]

I’ve credited you in my build, with: “Windows port by Teseng.” If you want some other name let me know.

I think you should definitively use SDL and let go of console shortcomings, that is allow for full color spectra, transparency and pixel precision translation, scaling and rotation of individual symbols, be they font derived or graphic tile bitmaps. That would allow for amazing gradual light/shadow effects and many other cool things, like showing bigger monsters with bigger font, change their orientation and for example turn them horizontal if they fall down or crawl, lean them left-right to make them wobble if they stumble, make them half size if kneeling, overly finer resolution graphics for special effects like bullet trails, blood splashes, pixel particle explosions, speech bubbles with finer font than graphics tiles themselves…

And you could also have sound library to go along with it. See my mod for some hints on how to go about it if you never fiddled with music and sound effects before:

DOWNLOAD (09. May): MGSmod rev.34b

With SDL all that is easy, simpler than with DX, and perhaps most importantly it’s portable.