Crossfire Server, Trunk  R213250
los.c
Go to the documentation of this file.
1 /*
2  * Crossfire -- cooperative multi-player graphical RPG and adventure game
3  *
4  * Copyright (c) 1999-2014 Mark Wedel and the Crossfire Development Team
5  * Copyright (c) 1992 Frank Tore Johansen
6  *
7  * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
8  * welcome to redistribute it under certain conditions. For details, please
9  * see COPYING and LICENSE.
10  *
11  * The authors can be reached via e-mail at <crossfire@metalforge.org>.
12  */
13 
19 #include "global.h"
20 
21 #include <math.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
32 #define SPACE_BLOCK 0.5
33 
34 typedef struct blstr {
35  int x[4], y[4];
36  int index;
37 } blocks;
38 
40 
41 static void expand_lighted_sight(object *op);
42 
61 static void set_block(int x, int y, int bx, int by) {
62  int index = block[x][y].index, i;
63 
64  /* Due to flipping, we may get duplicates - better safe than sorry.
65  */
66  for (i = 0; i < index; i++) {
67  if (block[x][y].x[i] == bx
68  && block[x][y].y[i] == by)
69  return;
70  }
71 
72  block[x][y].x[index] = bx;
73  block[x][y].y[index] = by;
74  block[x][y].index++;
75 #ifdef LOS_DEBUG
76  LOG(llevDebug, "setblock: added %d %d -> %d %d (%d)\n", x, y, bx, by, block[x][y].index);
77 #endif
78 }
79 
88 void init_block(void) {
89  int x, y, dx, dy, i;
90  static const int block_x[3] = {
91  -1, -1, 0
92  }, block_y[3] = {
93  -1, 0, -1
94  };
95 
96  for (x = 0; x < MAP_CLIENT_X; x++)
97  for (y = 0; y < MAP_CLIENT_Y; y++) {
98  block[x][y].index = 0;
99  }
100 
101  /* The table should be symmetric, so only do the upper left
102  * quadrant - makes the processing easier.
103  */
104  for (x = 1; x <= MAP_CLIENT_X/2; x++) {
105  for (y = 1; y <= MAP_CLIENT_Y/2; y++) {
106  /* center space never blocks */
107  if (x == MAP_CLIENT_X/2 && y == MAP_CLIENT_Y/2)
108  continue;
109 
110  for (i = 0; i < 3; i++) {
111  dx = x+block_x[i];
112  dy = y+block_y[i];
113 
114  /* If its a straight line, its blocked */
115  if ((dx == x && x == MAP_CLIENT_X/2)
116  || (dy == y && y == MAP_CLIENT_Y/2)) {
117  /* For simplicity, we mirror the coordinates to block the other
118  * quadrants.
119  */
120  set_block(x, y, dx, dy);
121  if (x == MAP_CLIENT_X/2) {
122  set_block(x, MAP_CLIENT_Y-y-1, dx, MAP_CLIENT_Y-dy-1);
123  } else if (y == MAP_CLIENT_Y/2) {
124  set_block(MAP_CLIENT_X-x-1, y, MAP_CLIENT_X-dx-1, dy);
125  }
126  } else {
127  float d1, s, l;
128 
129  /* We use the algorihm that found out how close the point
130  * (x,y) is to the line from dx,dy to the center of the viewable
131  * area. l is the distance from x,y to the line.
132  * r is more a curiosity - it lets us know what direction (left/right)
133  * the line is off
134  */
135 
136  d1 = (float)(pow(MAP_CLIENT_X/2-dx, 2)+pow(MAP_CLIENT_Y/2-dy, 2));
137  s = (float)((dy-y)*(MAP_CLIENT_X/2-dx)-(dx-x)*(MAP_CLIENT_Y/2-dy))/d1;
138  l = FABS(sqrt(d1)*s);
139 
140  if (l <= SPACE_BLOCK) {
141  /* For simplicity, we mirror the coordinates to block the other
142  * quadrants.
143  */
144  set_block(x, y, dx, dy);
145  set_block(MAP_CLIENT_X-x-1, y, MAP_CLIENT_X-dx-1, dy);
146  set_block(x, MAP_CLIENT_Y-y-1, dx, MAP_CLIENT_Y-dy-1);
147  set_block(MAP_CLIENT_X-x-1, MAP_CLIENT_Y-y-1, MAP_CLIENT_X-dx-1, MAP_CLIENT_Y-dy-1);
148  }
149  }
150  }
151  }
152  }
153 }
154 
170 static void set_wall(player *pl, int x, int y) {
171  // Get this outside the loop -- now we can recycle x and y in the loop
172  // Trying less dereferencing for better efficiency
173  // Equivalent to &(block[x][y]), but faster.
174  const blocks * const at = block[x] + y;
175  /*
176  * Assume the mapsize will not change mid-drawing. This seemed to work in my testing
177  * Daniel Hawkins 2019-12-03
178  */
179  const int map_sock_x = (MAP_CLIENT_X-pl->socket.mapx)>>1,
180  map_sock_y = (MAP_CLIENT_Y-pl->socket.mapy)>>1;
181  for (int i = 0; i < at->index; i++) {
182  int dx = at->x[i], dy = at->y[i];
183 
184  /* x, y are the values as adjusted to be in the
185  * socket look structure. Since the values are copied,
186  * we can safely store this in the x and y passed to the function,
187  * since they were only needed to find the element of the blocks array.
188  */
189  x = dx-map_sock_x;
190  y = dy-map_sock_y;
191 
192  if (x < 0 || x >= pl->socket.mapx
193  || y < 0 || y >= pl->socket.mapy
194  /* If already set to 100, we probably hit this space through recursion already. */
195  || pl->blocked_los[x][y] == 100)
196  continue;
197  /* we need to adjust to the fact that the socket
198  * code wants the los to start from the 0,0
199  * and not be relative to middle of los array.
200  */
201  pl->blocked_los[x][y] = 100;
202  set_wall(pl, dx, dy);
203  }
204 }
205 
217 static void check_wall(object *op, int x, int y) {
218  int ax, ay;
219 
220  if (!block[x][y].index)
221  return;
222 
223  /* ax, ay are coordinates as indexed into the look window */
224  ax = x-(MAP_CLIENT_X-op->contr->socket.mapx)/2;
225  ay = y-(MAP_CLIENT_Y-op->contr->socket.mapy)/2;
226 
227  /* If the converted coordinates are outside the viewable
228  * area for the client, return now.
229  */
230  if (ax < 0 || ay < 0 || ax >= op->contr->socket.mapx || ay >= op->contr->socket.mapy)
231  return;
232 
233  /* If this space is already blocked, prune the processing - presumably
234  * whatever has set this space to be blocked has done the work and already
235  * done the dependency chain.
236  */
237  if (op->contr->blocked_los[ax][ay] == 100)
238  return;
239 
240 
241  if (get_map_flags(op->map, NULL, op->x+x-MAP_CLIENT_X/2, op->y+y-MAP_CLIENT_Y/2, NULL, NULL)&(P_BLOCKSVIEW|P_OUT_OF_MAP))
242  set_wall(op->contr, x, y);
243 }
244 
253  /* This is safer than using the socket->mapx, mapy because
254  * we index the blocked_los as a 2 way array, so clearing
255  * the first z spaces may not not cover the spaces we are
256  * actually going to use
257  */
258  (void)memset((void *)pl->blocked_los, 0, MAP_CLIENT_X*MAP_CLIENT_Y);
259 }
260 
272 static void expand_sight(object *op) {
273  int i, x, y, dx, dy;
274 
275  for (x = 1; x < op->contr->socket.mapx-1; x++) /* loop over inner squares */
276  for (y = 1; y < op->contr->socket.mapy-1; y++) {
277  if (!op->contr->blocked_los[x][y]
278  && !(get_map_flags(op->map, NULL,
279  op->x-op->contr->socket.mapx/2+x,
280  op->y-op->contr->socket.mapy/2+y,
281  NULL, NULL)&(P_BLOCKSVIEW|P_OUT_OF_MAP))) {
282  for (i = 1; i <= 8; i += 1) { /* mark all directions */
283  dx = x+freearr_x[i];
284  dy = y+freearr_y[i];
285  if (op->contr->blocked_los[dx][dy] > 0) /* for any square blocked */
286  op->contr->blocked_los[dx][dy] = -1;
287  }
288  }
289  }
290 
291  if (MAP_DARKNESS(op->map) > 0) /* player is on a dark map */
293 
294 
295  /* clear mark squares */
296  for (x = 0; x < op->contr->socket.mapx; x++)
297  for (y = 0; y < op->contr->socket.mapy; y++)
298  if (op->contr->blocked_los[x][y] < 0)
299  op->contr->blocked_los[x][y] = 0;
300 }
301 
315 int has_carried_lights(const object *op) {
316  /* op may glow! */
317  if (op->glow_radius > 0)
318  return 1;
319 
320  return 0;
321 }
322 
329 static void expand_lighted_sight(object *op) {
330  int x, y, darklevel, ax, ay, basex, basey, mflags, light, x1, y1;
331  mapstruct *m = op->map;
332  int16_t nx, ny;
333 
334  darklevel = MAP_DARKNESS(m);
335 
336  /* If the player can see in the dark, lower the darklevel for him */
337  if (QUERY_FLAG(op, FLAG_SEE_IN_DARK))
338  darklevel -= 2;
339 
340  /* add light, by finding all (non-null) nearby light sources, then
341  * mark those squares specially. If the darklevel<1, there is no
342  * reason to do this, so we skip this function
343  */
344  if (darklevel < 1)
345  return;
346 
347  /* Do a sanity check. If not valid, some code below may do odd
348  * things.
349  */
350  if (darklevel > MAX_DARKNESS) {
351  LOG(llevError, "Map darkness for %s on %s is too high (%d)\n", op->name, op->map->path, darklevel);
352  darklevel = MAX_DARKNESS;
353  }
354 
355  /* First, limit player furthest (unlighted) vision */
356  for (x = 0; x < op->contr->socket.mapx; x++)
357  for (y = 0; y < op->contr->socket.mapy; y++)
358  if (op->contr->blocked_los[x][y] != 100)
360 
361  /* the spaces[] darkness value contains the information we need.
362  * Only process the area of interest.
363  * the basex, basey values represent the position in the op->contr->blocked_los
364  * array. Its easier to just increment them here (and start with the right
365  * value) than to recalculate them down below.
366  */
367  for (x = (op->x-op->contr->socket.mapx/2-MAX_LIGHT_RADII), basex = -MAX_LIGHT_RADII;
368  x <= (op->x+op->contr->socket.mapx/2+MAX_LIGHT_RADII); x++, basex++) {
369  for (y = (op->y-op->contr->socket.mapy/2-MAX_LIGHT_RADII), basey = -MAX_LIGHT_RADII;
370  y <= (op->y+op->contr->socket.mapy/2+MAX_LIGHT_RADII); y++, basey++) {
371  m = op->map;
372  nx = x;
373  ny = y;
374 
375  mflags = get_map_flags(m, &m, nx, ny, &nx, &ny);
376 
377  if (mflags&P_OUT_OF_MAP)
378  continue;
379 
380  /* This space is providing light, so we need to brighten up the
381  * spaces around here.
382  */
383  light = GET_MAP_LIGHT(m, nx, ny);
384  if (light != 0) {
385  for (ax = basex-light; ax <= basex+light; ax++) {
386  if (ax < 0 || ax >= op->contr->socket.mapx)
387  continue;
388  for (ay = basey-light; ay <= basey+light; ay++) {
389  if (ay < 0 || ay >= op->contr->socket.mapy)
390  continue;
391 
392  /* If the space is fully blocked, do nothing. Otherwise, we
393  * brighten the space. The further the light is away from the
394  * source (basex-x), the less effect it has. Though light used
395  * to dim in a square manner, it now dims in a circular manner
396  * using the the pythagorean theorem. glow_radius still
397  * represents the radius
398  */
399  if (op->contr->blocked_los[ax][ay] != 100) {
400  x1 = abs(basex-ax)*abs(basex-ax);
401  y1 = abs(basey-ay)*abs(basey-ay);
402  if (light > 0)
403  op->contr->blocked_los[ax][ay] -= MAX((light-isqrt(x1+y1)), 0);
404  if (light < 0)
405  op->contr->blocked_los[ax][ay] -= MIN((light+isqrt(x1+y1)), 0);
406  }
407  } /* for ay */
408  } /* for ax */
409  } /* if this space is providing light */
410  } /* for y */
411  } /* for x */
412 
413  /* Outdoor should never really be completely pitch black dark like
414  * a dungeon, so let the player at least see a little around themselves
415  */
416  if (op->map->outdoor && darklevel > (MAX_DARKNESS-3)) {
417  if (op->contr->blocked_los[op->contr->socket.mapx/2][op->contr->socket.mapy/2] > (MAX_DARKNESS-3))
418  op->contr->blocked_los[op->contr->socket.mapx/2][op->contr->socket.mapy/2] = MAX_DARKNESS-3;
419 
420  for (x = -1; x <= 1; x++)
421  for (y = -1; y <= 1; y++) {
422  if (op->contr->blocked_los[x+op->contr->socket.mapx/2][y+op->contr->socket.mapy/2] > (MAX_DARKNESS-2))
423  op->contr->blocked_los[x+op->contr->socket.mapx/2][y+op->contr->socket.mapy/2] = MAX_DARKNESS-2;
424  }
425  }
426  /* grant some vision to the player, based on the darklevel */
427  for (x = darklevel-MAX_DARKNESS; x < MAX_DARKNESS+1-darklevel; x++)
428  for (y = darklevel-MAX_DARKNESS; y < MAX_DARKNESS+1-darklevel; y++)
429  if (!(op->contr->blocked_los[x+op->contr->socket.mapx/2][y+op->contr->socket.mapy/2] == 100))
430  op->contr->blocked_los[x+op->contr->socket.mapx/2][y+op->contr->socket.mapy/2] -= MAX(0, 6-darklevel-MAX(abs(x), abs(y)));
431 }
432 
442 static void blinded_sight(player *pl) {
443  int x, y;
444 
445  for (x = 0; x < pl->socket.mapx; x++)
446  for (y = 0; y < pl->socket.mapy; y++)
447  pl->blocked_los[x][y] = 100;
448 
449  pl->blocked_los[pl->socket.mapx/2][pl->socket.mapy/2] = 0;
450 }
451 
459 void update_los(object *op) {
460  int dx = op->contr->socket.mapx/2, dy = op->contr->socket.mapy/2, x, y;
461 
462  if (QUERY_FLAG(op, FLAG_REMOVED))
463  return;
464 
465  clear_los(op->contr);
466  if (QUERY_FLAG(op, FLAG_WIZ) /* || XRAYS(op) */)
467  return;
468 
469  /* For larger maps, this is more efficient than the old way which
470  * used the chaining of the block array. Since many space views could
471  * be blocked by different spaces in front, this mean that a lot of spaces
472  * could be examined multile times, as each path would be looked at.
473  */
474  for (x = (MAP_CLIENT_X-op->contr->socket.mapx)/2+1; x < (MAP_CLIENT_X+op->contr->socket.mapx)/2-1; x++)
475  for (y = (MAP_CLIENT_Y-op->contr->socket.mapy)/2+1; y < (MAP_CLIENT_Y+op->contr->socket.mapy)/2-1; y++)
476  check_wall(op, x, y);
477 
478 
479  /* do the los of the player. 3 (potential) cases */
480  if (QUERY_FLAG(op, FLAG_BLIND)) /* player is blind */
481  blinded_sight(op->contr);
482  else
483  expand_sight(op);
484 
485  if (QUERY_FLAG(op, FLAG_XRAYS)) {
486  int x, y;
487  for (x = -2; x <= 2; x++)
488  for (y = -2; y <= 2; y++)
489  op->contr->blocked_los[dx+x][dy+y] = 0;
490  }
491 }
492 
509  player *pl;
510 
511  for (pl = first_player; pl != NULL; pl = pl->next) {
512  if (pl->ob->map == map)
513  pl->do_los = 1;
514  }
515 }
516 
536 void update_all_los(const mapstruct *map, int x, int y) {
537  player *pl;
538 
539  for (pl = first_player; pl != NULL; pl = pl->next) {
540  /* Player should not have a null map, but do this
541  * check as a safety
542  */
543  if (!pl->ob->map)
544  continue;
545 
546  /* Same map is simple case - see if pl is close enough.
547  * Note in all cases, we did the check for same map first,
548  * and then see if the player is close enough and update
549  * los if that is the case. If the player is on the
550  * corresponding map, but not close enough, then the
551  * player can't be on another map that may be closer,
552  * so by setting it up this way, we trim processing
553  * some.
554  */
555  if (pl->ob->map == map) {
556  if ((abs(pl->ob->x-x) <= pl->socket.mapx/2)
557  && (abs(pl->ob->y-y) <= pl->socket.mapy/2))
558  pl->do_los = 1;
559  }
560  /* Now we check to see if player is on adjacent
561  * maps to the one that changed and also within
562  * view. The tile_maps[] could be null, but in that
563  * case it should never match the pl->ob->map, so
564  * we want ever try to dereference any of the data in it.
565  */
566 
567  /* The logic for 0 and 3 is to see how far the player is
568  * from the edge of the map (height/width) - pl->ob->(x,y)
569  * and to add current position on this map - that gives a
570  * distance.
571  * For 1 and 2, we check to see how far the given
572  * coordinate (x,y) is from the corresponding edge,
573  * and then add the players location, which gives
574  * a distance.
575  */
576  else if (pl->ob->map == map->tile_map[0]) {
577  if ((abs(pl->ob->x-x) <= pl->socket.mapx/2)
578  && (abs(y+MAP_HEIGHT(map->tile_map[0])-pl->ob->y) <= pl->socket.mapy/2))
579  pl->do_los = 1;
580  } else if (pl->ob->map == map->tile_map[2]) {
581  if ((abs(pl->ob->x-x) <= pl->socket.mapx/2)
582  && (abs(pl->ob->y+MAP_HEIGHT(map)-y) <= pl->socket.mapy/2))
583  pl->do_los = 1;
584  } else if (pl->ob->map == map->tile_map[1]) {
585  if ((abs(pl->ob->x+MAP_WIDTH(map)-x) <= pl->socket.mapx/2)
586  && (abs(pl->ob->y-y) <= pl->socket.mapy/2))
587  pl->do_los = 1;
588  } else if (pl->ob->map == map->tile_map[3]) {
589  if ((abs(x+MAP_WIDTH(map->tile_map[3])-pl->ob->x) <= pl->socket.mapx/2)
590  && (abs(pl->ob->y-y) <= pl->socket.mapy/2))
591  pl->do_los = 1;
592  }
593  }
594 }
595 
606 void print_los(object *op) {
607  int x, y;
608  char buf[MAP_CLIENT_X*2+20], buf2[10];
609 
610  snprintf(buf, sizeof(buf), "[fixed] ");
611  for (x = 0; x < op->contr->socket.mapx; x++) {
612  snprintf(buf2, sizeof(buf2), "%2d", x);
613  strncat(buf, buf2, sizeof(buf)-strlen(buf)-1);
614  }
616  for (y = 0; y < op->contr->socket.mapy; y++) {
617  snprintf(buf, sizeof(buf), "[fixed]%2d:", y);
618  for (x = 0; x < op->contr->socket.mapx; x++) {
619  snprintf(buf2, sizeof(buf2), " %1d", op->contr->blocked_los[x][y] == 100 ? 1 : 0);
620  strncat(buf, buf2, sizeof(buf)-strlen(buf)-1);
621  }
623  }
624 }
625 
636 void make_sure_seen(const object *op) {
637  player *pl;
638 
639  for (pl = first_player; pl; pl = pl->next)
640  if (pl->ob->map == op->map
641  && pl->ob->y-pl->socket.mapy/2 <= op->y
642  && pl->ob->y+pl->socket.mapy/2 >= op->y
643  && pl->ob->x-pl->socket.mapx/2 <= op->x
644  && pl->ob->x+pl->socket.mapx/2 >= op->x)
645  pl->blocked_los[pl->socket.mapx/2+op->x-pl->ob->x][pl->socket.mapy/2+op->y-pl->ob->y] = 0;
646 }
647 
659 void make_sure_not_seen(const object *op) {
660  player *pl;
661 
662  for (pl = first_player; pl; pl = pl->next)
663  if (pl->ob->map == op->map
664  && pl->ob->y-pl->socket.mapy/2 <= op->y
665  && pl->ob->y+pl->socket.mapy/2 >= op->y
666  && pl->ob->x-pl->socket.mapx/2 <= op->x
667  && pl->ob->x+pl->socket.mapx/2 >= op->x)
668  pl->do_los = 1;
669 }
char path[HUGE_BUF]
Definition: map.h:365
Definition: player.h:92
#define FLAG_SEE_IN_DARK
Definition: define.h:338
static void set_wall(player *pl, int x, int y)
Definition: los.c:170
void init_block(void)
Definition: los.c:88
static void expand_lighted_sight(object *op)
Definition: los.c:329
#define MAX_LIGHT_RADII
Definition: define.h:466
#define FABS(x)
Definition: define.h:22
struct mapdef * tile_map[4]
Definition: map.h:364
#define MAP_HEIGHT(m)
Definition: map.h:80
socket_struct socket
Definition: player.h:94
short freearr_x[SIZEOFFREE]
Definition: object.c:65
#define MAX_DARKNESS
Definition: define.h:470
static void expand_sight(object *op)
Definition: los.c:272
int isqrt(int n)
Definition: utils.c:586
#define MAX(x, y)
Definition: compat.h:20
void make_sure_seen(const object *op)
Definition: los.c:636
static void check_wall(object *op, int x, int y)
Definition: los.c:217
void draw_ext_info(int flags, int pri, const object *pl, uint8_t type, uint8_t subtype, const char *message)
Definition: main.c:311
#define MIN(x, y)
Definition: compat.h:17
#define FLAG_REMOVED
Definition: define.h:232
short freearr_y[SIZEOFFREE]
Definition: object.c:71
Definition: los.c:34
int16_t y
Definition: object.h:326
#define MSG_TYPE_COMMAND
Definition: newclient.h:379
static void blinded_sight(player *pl)
Definition: los.c:442
static blocks block[MAP_CLIENT_X][MAP_CLIENT_Y]
Definition: los.c:39
#define MAP_DARKNESS(m)
Definition: map.h:75
signed short int16_t
Definition: win32.h:160
void update_all_los(const mapstruct *map, int x, int y)
Definition: los.c:536
void clear_los(player *pl)
Definition: los.c:252
struct mapdef * map
Definition: object.h:297
#define snprintf
Definition: win32.h:46
static void set_block(int x, int y, int bx, int by)
Definition: los.c:61
const char * name
Definition: object.h:311
#define GET_MAP_LIGHT(M, X, Y)
Definition: map.h:165
#define P_OUT_OF_MAP
Definition: map.h:251
#define SPACE_BLOCK
Definition: los.c:32
struct pl * contr
Definition: object.h:276
#define FLAG_XRAYS
Definition: define.h:301
int8_t blocked_los[MAP_CLIENT_X][MAP_CLIENT_Y]
Definition: player.h:159
#define QUERY_FLAG(xyz, p)
Definition: define.h:225
#define FLAG_WIZ
Definition: define.h:231
#define MSG_TYPE_COMMAND_DEBUG
Definition: newclient.h:508
int16_t x
Definition: object.h:326
uint8_t mapx
Definition: newserver.h:116
#define MAP_CLIENT_X
Definition: config.h:237
int y[4]
Definition: los.c:35
int x[4]
Definition: los.c:35
object * ob
Definition: player.h:158
int has_carried_lights(const object *op)
Definition: los.c:315
#define MAP_CLIENT_Y
Definition: config.h:238
uint32_t outdoor
Definition: map.h:340
#define FLAG_BLIND
Definition: define.h:337
void print_los(object *op)
Definition: los.c:606
void update_all_map_los(mapstruct *map)
Definition: los.c:508
#define MAP_WIDTH(m)
Definition: map.h:78
int index
Definition: los.c:36
uint32_t do_los
Definition: player.h:126
void update_los(object *op)
Definition: los.c:459
EXTERN player * first_player
Definition: global.h:117
struct pl * next
Definition: player.h:93
int8_t glow_radius
Definition: object.h:365
int get_map_flags(mapstruct *oldmap, mapstruct **newmap, int16_t x, int16_t y, int16_t *nx, int16_t *ny)
Definition: map.c:310
#define NDI_UNIQUE
Definition: newclient.h:245
void LOG(LogLevel logLevel, const char *format,...)
Definition: logger.c:51
#define P_BLOCKSVIEW
Definition: map.h:226
Definition: map.h:325
struct blstr blocks
void make_sure_not_seen(const object *op)
Definition: los.c:659
uint8_t mapy
Definition: newserver.h:116