Simutrans-Squirrel-API  r11815
api_map_objects.cc
Go to the documentation of this file.
1 /*
2  * This file is part of the Simutrans project under the Artistic License.
3  * (see LICENSE.txt)
4  */
5 
6 #include "api.h"
7 
11 #include "api_simple.h"
12 #include "../api_class.h"
13 #include "../api_function.h"
14 
15 #include "../../obj/simobj.h"
16 #include "../../obj/depot.h"
17 #include "../../world/simworld.h"
18 #include "../../ground/grund.h"
19 #include "../../dataobj/scenario.h"
20 #include "../../descriptor/ground_desc.h"
21 #include "../../obj/baum.h"
22 #include "../../obj/bruecke.h"
23 #include "../../obj/gebaeude.h"
24 #include "../../obj/field.h"
25 #include "../../obj/label.h"
26 #include "../../obj/leitung2.h"
27 #include "../../obj/roadsign.h"
28 #include "../../obj/signal.h"
29 #include "../../obj/tunnel.h"
30 #include "../../obj/wayobj.h"
31 #include "../../player/simplay.h"
32 
33 // for depot tools
34 #include "../../simconvoi.h"
35 #include "../../tool/simmenu.h"
36 #include "../../descriptor/building_desc.h"
37 #include "../../descriptor/vehicle_desc.h"
38 
39 
40 using namespace script_api;
41 
42 // use pointers to obj_t_tag[ <type> ] to tag the obj_t classes
43 static uint8 obj_t_tag[256];
44 
45 /*
46  * template struct to bind obj_t::typ to obj_t classes
47  */
48 template<class D> struct bind_code;
49 
50 /*
51  * Class to templatify access of obj_t objects
52  */
53 template<class D> struct access_objs {
54 
55  /*
56  * Access object: check whether object is still on this tile.
57  */
58  static D* get_by_pos(HSQUIRRELVM vm, SQInteger index)
59  {
60  SQUserPointer tag = obj_t_tag + bind_code<D>::objtype;
61  SQUserPointer p = NULL;
62  if (SQ_SUCCEEDED(sq_getinstanceup(vm, index, &p, tag)) && p) {
63  D *obj = static_cast<D*>(p);
64  koord3d pos = param<koord3d>::get(vm, index);
65  grund_t *gr = welt->lookup(pos);
66  if (gr && gr->obj_ist_da(obj)) {
67  return obj;
68  }
69  else {
70  // object or tile disappeared: clear userpointer
71  sq_setinstanceup(vm, index, NULL);
72  }
73  sq_raise_error(vm, "Object of type %s vanished from (%s).", param<D*>::squirrel_type(), pos.get_str());
74  }
75  else {
76  sq_raise_error(vm, "Object is not of type %s.", param<D*>::squirrel_type);
77  }
78  return NULL;
79  }
80 
81  /*
82  * Create instance: call constructor with coordinates.
83  */
84  static SQInteger push_with_pos(HSQUIRRELVM vm, D* const& obj)
85  {
86  if (obj == NULL) {
87  sq_pushnull(vm);
88  return 1;
89  }
90  koord pos = obj->get_pos().get_2d();
91  coordinate_transform_t::koord_w2sq(pos);
92  sint16 x = pos.x;
93  sint16 y = pos.y;
94  sint8 z = obj->get_pos().z;
95  if (bind_code<D>::objtype == obj_t::obj) {
96  // generic object, object type as fourth parameter
97  if (!SQ_SUCCEEDED(push_instance(vm, script_api::param<D*>::squirrel_type(), x, y, z, obj->get_typ()))) {
98  return SQ_ERROR;
99  }
100  }
101  else if (bind_code<D>::objtype != obj->get_typ()) {
102  // obj is instance of derived class
103  return script_api::param<obj_t*>::push(vm, obj);
104  }
105  else {
106  // specific object with its own constructor, type already preset as default parameter
107  if (!SQ_SUCCEEDED(push_instance(vm, script_api::param<D*>::squirrel_type(), x, y, z))) {
108  return SQ_ERROR;
109  }
110  }
111  sq_setinstanceup(vm, -1, obj);
112  return 1;
113  }
114 
115 };
116 
117 
118 SQInteger exp_obj_pos_constructor(HSQUIRRELVM vm) // parameters: sint16 x, sint16 y, sint8 z, obj_t::typ type
119 {
120  // get coordinates
121  sint16 x = param<sint16>::get(vm, 2);
122  sint16 y = param<sint16>::get(vm, 3);
123  sint8 z = param<sint16>::get(vm, 4);
124  // set coordinates
125  set_slot(vm, "x", x, 1);
126  set_slot(vm, "y", y, 1);
127  koord pos(x,y);
128  coordinate_transform_t::koord_sq2w(pos);
129  // find tile - some objects have larger z-coordinate (e.g., on foundations)
130  grund_t *gr = welt->lookup(koord3d(pos, z));
131  for(uint8 i=1, end = ground_desc_t::double_grounds ? 2 : 1; gr == NULL && i<=end; i++) {
132  gr = welt->lookup(koord3d(pos, z-i));
133  }
134  // find object and set instance up
135  if (gr) {
136  // correct z-coordinate
137  set_slot(vm, "z", gr->get_pos().z, 1);
138  // search for object
139  obj_t::typ type = (obj_t::typ)param<uint8>::get(vm, 5);
140  obj_t *obj = NULL;
141  if (type == obj_t::roadsign || type == obj_t::signal) {
142  // special treatment of signals/roadsigns
143  obj = gr->suche_obj(obj_t::roadsign);
144  if (obj == NULL) {
145  obj = gr->suche_obj(obj_t::signal);
146  }
147  }
148  else if (type == obj_t::pumpe || type == obj_t::senke) {
149  obj = gr->suche_obj(obj_t::pumpe);
150  if (obj == NULL) {
151  obj = gr->suche_obj(obj_t::senke);
152  }
153  }
154  else if (type != obj_t::old_airdepot) { // special treatment of depots
155  obj = gr->suche_obj(type);
156  }
157  else {
158  obj = gr->get_depot();
159  }
160  if (obj) {
161  sq_setinstanceup(vm, 1, obj);
162  return SQ_OK;
163  }
164  }
165  return sq_raise_error(vm, "No object of requested type on tile (or no tile at this position)");
166 }
167 
168 template<> struct bind_code<obj_t> { static const uint8 objtype = obj_t::obj; };
169 
170 // macro to implement get and push for obj_t's with position
171 #define getpush_obj_pos(D, type) \
172  D* script_api::param<D*>::get(HSQUIRRELVM vm, SQInteger index) \
173  { \
174  return access_objs<D>::get_by_pos(vm, index); \
175  } \
176  SQInteger script_api::param<D*>::push(HSQUIRRELVM vm, D* const& obj) \
177  { \
178  return access_objs<D>::push_with_pos(vm, obj); \
179  } \
180  template<> struct bind_code<D> { static const uint8 objtype = type; };
181 
182 // implementation of get and push by macros
183 getpush_obj_pos(baum_t, obj_t::baum);
184 getpush_obj_pos(gebaeude_t, obj_t::gebaeude);
185 getpush_obj_pos(label_t, obj_t::label);
186 getpush_obj_pos(weg_t, obj_t::way);
187 getpush_obj_pos(leitung_t, obj_t::leitung);
188 getpush_obj_pos(field_t, obj_t::field);
189 getpush_obj_pos(wayobj_t, obj_t::wayobj);
190 getpush_obj_pos(bruecke_t, obj_t::bruecke);
191 getpush_obj_pos(tunnel_t, obj_t::tunnel);
192 
193 namespace script_api {
194  // each depot has its own class
195  declare_specialized_param(depot_t*, "t|x|y", "depot_x");
196  declare_specialized_param(airdepot_t*, "t|x|y", "depot_x");
197  declare_specialized_param(narrowgaugedepot_t*, "t|x|y", "depot_x");
198  declare_specialized_param(bahndepot_t*, "t|x|y", "depot_x");
199  declare_specialized_param(strassendepot_t*, "t|x|y", "depot_x");
200  declare_specialized_param(schiffdepot_t*, "t|x|y", "depot_x");
201  declare_specialized_param(monoraildepot_t*, "t|x|y", "depot_x");
202  declare_specialized_param(tramdepot_t*, "t|x|y", "depot_x");
203  declare_specialized_param(maglevdepot_t*, "t|x|y", "depot_x");
204 
205  // map roadsign_t and signal_t to the same class
206  declare_specialized_param(roadsign_t*, "t|x|y", "sign_x");
207  declare_specialized_param(signal_t*, "t|x|y", "sign_x");
208 
209  // map all to one transformer class
210  declare_specialized_param(pumpe_t*, "t|x|y", "transformer_x");
211  declare_specialized_param(senke_t*, "t|x|y", "transformer_x");
212 };
213 
214 // base depot class, use old_airdepot as identifier here
215 getpush_obj_pos(depot_t, obj_t::old_airdepot);
216 // now all the derived classes
217 getpush_obj_pos(airdepot_t, obj_t::airdepot);
218 getpush_obj_pos(narrowgaugedepot_t, obj_t::narrowgaugedepot);
219 getpush_obj_pos(bahndepot_t, obj_t::bahndepot);
220 getpush_obj_pos(strassendepot_t, obj_t::strassendepot);
221 getpush_obj_pos(schiffdepot_t, obj_t::schiffdepot);
222 getpush_obj_pos(monoraildepot_t, obj_t::monoraildepot);
223 getpush_obj_pos(tramdepot_t, obj_t::tramdepot);
224 getpush_obj_pos(maglevdepot_t, obj_t::maglevdepot);
225 // roadsigns/signals
226 getpush_obj_pos(roadsign_t, obj_t::roadsign);
227 getpush_obj_pos(signal_t, obj_t::signal);
228 // powerlines
229 getpush_obj_pos(pumpe_t, obj_t::pumpe);
230 getpush_obj_pos(senke_t, obj_t::senke);
231 
232 
233 #define case_resolve_obj(D) \
234  case bind_code<D>::objtype: \
235  return script_api::param<D*>::push(vm, (D*)obj);
236 
237 // we have to resolve instances of derived classes here...
238 SQInteger script_api::param<obj_t*>::push(HSQUIRRELVM vm, obj_t* const& obj)
239 {
240  if (obj == NULL) {
241  sq_pushnull(vm);
242  return 1;
243  }
244  obj_t::typ type = obj->get_typ();
245  switch(type) {
246  case_resolve_obj(baum_t);
247  case_resolve_obj(gebaeude_t);
248  case_resolve_obj(label_t);
249  case_resolve_obj(weg_t);
250  case_resolve_obj(roadsign_t);
251  case_resolve_obj(signal_t);
252  case_resolve_obj(field_t);
253 
254  case_resolve_obj(airdepot_t);
255  case_resolve_obj(narrowgaugedepot_t);
256  case_resolve_obj(bahndepot_t);
257  case_resolve_obj(strassendepot_t);
258  case_resolve_obj(schiffdepot_t);
259  case_resolve_obj(monoraildepot_t);
260  case_resolve_obj(tramdepot_t);
261  case_resolve_obj(maglevdepot_t);
262 
263  case_resolve_obj(leitung_t);
264  case_resolve_obj(pumpe_t);
265  case_resolve_obj(senke_t);
266 
267  case_resolve_obj(wayobj_t);
268  case_resolve_obj(bruecke_t);
269  case_resolve_obj(tunnel_t);
270  default:
271  return access_objs<obj_t>::push_with_pos(vm, obj);
272  }
273 }
274 
275 obj_t* script_api::param<obj_t*>::get(HSQUIRRELVM vm, SQInteger index)
276 {
277  return access_objs<obj_t>::get_by_pos(vm, index);
278 }
280 // return way ribis, have to implement a wrapper, to correctly rotate ribi
281 static SQInteger get_way_ribi(HSQUIRRELVM vm)
282 {
283  weg_t *w = param<weg_t*>::get(vm, 1);
284  bool masked = param<bool>::get(vm, 2);
285 
286  ribi_t::ribi ribi = w ? (masked ? w->get_ribi() : w->get_ribi_unmasked() ) : 0;
287 
288  return param<my_ribi_t>::push(vm, ribi);
289 }
290 
291 
292 template<class D>
293 static SQInteger map_obj_to_string(HSQUIRRELVM vm) // parameters: obj
294 {
295  static cbuffer_t buf;
296  buf.clear();
297  koord3d pos = script_api::param<koord3d>::get(vm, 1);
298  D* obj = script_api::param<D*>::get(vm, 1);
299  buf.printf("%s@%s", script_api::param<D*>::squirrel_type(), pos.get_str());
300  if (obj == NULL) {
301  buf.append(" [invalid]");
302  }
303  sq_pushstring(vm, (const char*)buf, -1);
304  return 1;
305 }
307 
308 // create class
309 template<class D>
310 static void begin_obj_class(HSQUIRRELVM vm, const char* name, const char* base = NULL)
311 {
312  SQInteger res = create_class(vm, name, base);
313  if( !SQ_SUCCEEDED(res) ) {
314  // base class not found: maybe script_base.nut is not up-to-date
315  dbg->error( "begin_obj_class()", "Create class failed for %s. Base class %s missing. Please update simutrans (or just script/script_base.nut)!", name, base );
316  sq_raise_error(vm, "Create class failed for %s. Base class %s missing. Please update simutrans (or just script/script_base.nut)!", name, base);
317  }
318  uint8 objtype = bind_code<D>::objtype;
319  // store typetag to identify pointers
320  sq_settypetag(vm, -1, obj_t_tag + objtype);
321  // export constructor
322  register_function_fv(vm, exp_obj_pos_constructor, "constructor", 4, "xiiii", freevariable<uint8>(objtype));
323  // export _tostring method
324  register_function(vm, map_obj_to_string<D>, "_tostring", 1, "x");
325  // now functions can be registered
326 }
327 
328 // mark objects
329 static void mark_object(obj_t* obj)
330 {
331  obj->set_flag(obj_t::highlight);
332  obj->set_flag(obj_t::dirty);
333 }
334 static void unmark_object(obj_t* obj)
335 {
336  obj->clear_flag(obj_t::highlight);
337  obj->set_flag(obj_t::dirty);
338 }
339 static bool object_is_marked(obj_t* obj)
340 {
341  return obj->get_flag(obj_t::highlight);
342 }
343 
344 // markers / labels
345 static call_tool_work create_marker(koord pos, player_t* player, const char* text)
346 {
347  if (text == NULL) {
348  return "Cannot create label with text == null";
349  }
350  return call_tool_work(TOOL_MARKER | GENERAL_TOOL, text, 0, player, koord3d(pos, 0));
351 }
352 
353 static call_tool_init label_set_text(label_t *l, const char* text)
354 {
355  return command_rename(l->get_owner(), 'm', l->get_pos(), text);
356 }
357 
358 static const char* label_get_text(label_t* l)
359 {
360  if (l) {
361  if (grund_t *gr = welt->lookup(l->get_pos())) {
362  return gr->get_text();
363  }
364  }
365  return NULL;
366 }
367 
368 // roadsign
369 static bool roadsign_can_pass(const roadsign_t* rs, player_t* player)
370 {
371  return player && rs->get_desc()->is_private_way() ? (rs->get_player_mask() & (1<<player->get_player_nr()))!=0 : true;
372 }
373 
374 // depot
375 static call_tool_init depot_append_vehicle(depot_t *depot, player_t *player, convoihandle_t cnv, const vehicle_desc_t *desc)
376 {
377  if (desc == NULL) {
378  return "Invalid vehicle_desc_x provided";
379  }
380  // see depot_frame_t::image_from_storage_list: tool = 'a'
381  // see depot_t::call_depot_tool for command string composition
382  cbuffer_t buf;
383  buf.printf( "%c,%s,%hu,%s", 'a', depot->get_pos().get_str(), cnv.get_id(), desc->get_name());
384 
385  return call_tool_init(TOOL_CHANGE_DEPOT | SIMPLE_TOOL, buf, 0, player);
386 }
387 
388 static call_tool_init depot_start_convoy(depot_t *depot, player_t *player, convoihandle_t cnv)
389 {
390  // see depot_frame_t::action_triggered: tool = 'b'
391  // see depot_t::call_depot_tool for command string composition
392  cbuffer_t buf;
393  if (cnv.is_bound()) {
394  buf.printf( "%c,%s,%hu", 'b', depot->get_pos().get_str(), cnv->self.get_id());
395  }
396  else {
397  buf.printf( "%c,%s,%hu", 'B', depot->get_pos().get_str(), 0);
398  }
399 
400  return call_tool_init(TOOL_CHANGE_DEPOT | SIMPLE_TOOL, buf, 0, player);
401 }
402 
403 static vector_tpl<convoihandle_t> const& depot_get_convoy_list(depot_t *depot)
404 {
405  static vector_tpl<convoihandle_t> list;
406  list.clear();
407  if (depot==NULL) {
408  return list;
409  }
410  // fill list
411  slist_tpl<convoihandle_t> const& slist = depot->get_convoy_list();
412 
413  for(slist_tpl<convoihandle_t>::const_iterator i = slist.begin(), end = slist.end(); i!=end; ++i) {
414  list.append(*i);
415  }
416  return list;
417 }
418 
419 static vector_tpl<depot_t*> const& get_depot_list(player_t *player, waytype_t wt)
420 {
421  static vector_tpl<depot_t*> list;
422  list.clear();
423  // do the conversion of waytype_t to depot-type by linetype
424  simline_t::linetype line_type = simline_t::waytype_to_linetype(wt);
425  if (player == NULL || line_type == simline_t::MAX_LINE_TYPE) {
426  return list;
427  }
428  // fill list
429  const slist_tpl<depot_t*> &depot_list = depot_t::get_depot_list();
430 
431  for(depot_t* d : depot_list) {
432  if(d->get_line_type()==line_type && d->get_owner()==player) {
433  list.append(d);
434  }
435  }
436  return list;
437 }
438 
439 
440 static const fabrik_t* transformer_get_factory(leitung_t *lt)
441 {
442  if (pumpe_t *p = dynamic_cast<pumpe_t*>(lt)) {
443  return p->get_factory();
444  }
445  if (senke_t *s = dynamic_cast<senke_t*>(lt)) {
446  return s->get_factory();
447  }
448  return NULL;
449 }
450 
451 static bool leitung_is_connected(leitung_t* lt1, leitung_t* lt2)
452 {
453  return lt2 != NULL && lt1->get_net() == lt2->get_net();
454 }
455 
456 static bool field_is_deletable(field_t* f)
457 {
458  return f->get_removal_error( welt->get_public_player() ) == NULL;
459 }
460 
461 
462 static vector_tpl<sint64> const& get_way_stat(weg_t* weg, sint32 INDEX)
463 {
464  static vector_tpl<sint64> v;
465  v.clear();
466  if (weg && 0<=INDEX && INDEX<WAY_STAT_MAX) {
467  for(uint16 i = 0; i < MAX_WAY_STAT_MONTHS; i++) {
468  v.append( weg->get_stat(i, INDEX) );
469  }
470  }
471  return v;
472 }
473 
474 static vector_tpl<grund_t*> const& get_tile_list( gebaeude_t *gb )
475 {
476  static vector_tpl<grund_t*> list;
477  gb->get_tile_list( list );
478  return list;
479 }
480 
481 
482 void export_map_objects(HSQUIRRELVM vm)
483 {
488  begin_class(vm, "map_object_x", "coord3d,extend_get,ingame_object");
489  uint8 objtype = bind_code<obj_t>::objtype;
490  sq_settypetag(vm, -1, obj_t_tag + objtype);
501  register_function(vm, exp_obj_pos_constructor, "constructor", 5, "xiiii");
505  export_is_valid<obj_t*>(vm); //register_function("is_valid")
509  register_method(vm, &obj_t::get_owner, "get_owner");
513  register_method(vm, &obj_t::get_name, "get_name");
517  register_method(vm, &obj_t::get_waytype, "get_waytype");
521  register_method(vm, &obj_t::get_pos, "get_pos");
526  register_method(vm, &obj_t::get_removal_error, "is_removable");
530  register_method(vm, &obj_t::get_typ, "get_type");
534  register_method(vm, &mark_object, "mark", true);
538  register_method(vm, &unmark_object, "unmark", true);
542  register_method(vm, &object_is_marked, "is_marked", true);
543  end_class(vm);
544 
545 
549  begin_obj_class<baum_t>(vm, "tree_x", "map_object_x");
553  register_method(vm, &baum_t::get_age, "get_age");
557  register_method(vm, &baum_t::get_desc, "get_desc");
558 
559  end_class(vm);
560 
564  begin_obj_class<gebaeude_t>(vm, "building_x", "map_object_x");
568  register_method(vm, &gebaeude_t::get_fabrik, "get_factory");
572  register_method(vm, &gebaeude_t::get_stadt, "get_city");
576  register_method(vm, &gebaeude_t::is_townhall, "is_townhall");
580  register_method(vm, &gebaeude_t::is_headquarter, "is_headquarter");
584  register_method(vm, &gebaeude_t::is_monument, "is_monument");
589  register_method(vm, &gebaeude_t::get_passagier_level, "get_passenger_level");
594  register_method(vm, &gebaeude_t::get_mail_level, "get_mail_level");
598  register_method(vm, &gebaeude_t::get_tile, "get_desc");
602  register_method( vm, &get_tile_list, "get_tile_list", true );
606  register_method(vm, &gebaeude_t::is_same_building, "is_same_building");
607 
608  end_class(vm);
609 
614  begin_obj_class<depot_t>(vm, "depot_x", "building_x");
622  register_method(vm, depot_append_vehicle, "append_vehicle", true);
627  register_method(vm, depot_start_convoy, "start_convoy", true);
632  register_method_fv(vm, depot_start_convoy, "start_all_convoys", freevariable<convoihandle_t>(convoihandle_t()), true);
636  register_method(vm, &depot_get_convoy_list, "get_convoy_list", true);
643  STATIC register_method(vm, &get_depot_list, "get_depot_list", false, true);
644  end_class(vm);
645 
649  begin_obj_class<weg_t>(vm, "way_x", "map_object_x");
653  register_method(vm, &weg_t::hat_gehweg, "has_sidewalk");
657  register_method(vm, &weg_t::is_electrified, "is_electrified");
661  register_method(vm, &weg_t::has_sign, "has_sign");
665  register_method(vm, &weg_t::has_signal, "has_signal");
669  register_method(vm, &weg_t::has_wayobj, "has_wayobj");
673  register_method(vm, &weg_t::is_crossing, "is_crossing");
679  register_function_fv(vm, &get_way_ribi, "get_dirs", 1, "x", freevariable<bool>(false) );
685  register_function_fv(vm, &get_way_ribi, "get_dirs_masked", 1, "x", freevariable<bool>(true) );
689  register_method(vm, &weg_t::get_desc, "get_desc");
695  register_method(vm, &weg_t::get_max_speed, "get_max_speed");
700  register_method_fv(vm, &get_way_stat, "get_transported_goods", freevariable<sint32>(WAY_STAT_GOODS), true);
705  register_method_fv(vm, &get_way_stat, "get_convoys_passed", freevariable<sint32>(WAY_STAT_CONVOIS), true);
706  end_class(vm);
707 
708 
712  begin_obj_class<label_t>(vm, "label_x", "map_object_x");
720  STATIC register_method(vm, &create_marker, "create", false, true);
726  register_method(vm, &label_set_text, "set_text", true);
731  register_method(vm, &label_get_text, "get_text", true);
732 
733  end_class(vm);
734 
738  begin_obj_class<roadsign_t>(vm, "sign_x", "map_object_x");
742  register_method(vm, &roadsign_t::get_desc, "get_desc");
743 
749  register_method(vm, &roadsign_can_pass, "can_pass", true);
750  end_class(vm);
751 
756  begin_obj_class<leitung_t>(vm, "powerline_x", "map_object_x");
757 
762  register_method(vm, &leitung_is_connected, "is_connected", true);
763  end_class(vm);
764 
769  begin_obj_class<pumpe_t>(vm, "transformer_x", "powerline_x");
770 
774  register_method(vm, &transformer_get_factory, "get_factory", true);
775  end_class(vm);
776 
780  begin_obj_class<field_t>(vm, "field_x", "map_object_x");
786  register_method(vm, &field_is_deletable, "is_deletable", true);
791  register_method(vm, &field_t::get_factory, "get_factory");
792  end_class(vm);
793 
797  begin_obj_class<wayobj_t>(vm, "wayobj_x", "map_object_x");
801  register_method(vm, &wayobj_t::get_desc, "get_desc");
802  end_class(vm);
803 
807  begin_obj_class<bruecke_t>(vm, "bridge_x", "map_object_x");
811  register_method(vm, &bruecke_t::get_desc, "get_desc");
812  end_class(vm);
813 
817  begin_obj_class<tunnel_t>(vm, "tunnel_x", "map_object_x");
821  register_method(vm, &tunnel_t::get_desc, "get_desc");
822  end_class(vm);
823 }