Exemple #1
0
const Pic *GetObjectPic(const int id, Vec2i *offset)
{
	const TObject *obj = CArrayGet(&gObjs, id);

	Pic *pic = NULL;
	// Try to get new pic if available
	if (obj->picName && obj->picName[0] != '\0')
	{
		pic = PicManagerGetPic(&gPicManager, obj->picName);
	}
	// Use new pic offset if old one unavailable
	const TOffsetPic *ofpic = obj->pic;
	if (!ofpic)
	{
		// If new one also unavailable, bail
		if (pic == NULL)
		{
			return NULL;
		}
		*offset = Vec2iScaleDiv(pic->size, -2);
	}
	else if (pic == NULL)
	{
		// Default old pic
		pic = PicManagerGetFromOld(&gPicManager, ofpic->picIndex);
		*offset = pic->offset;
	}
	if (ofpic != NULL)
	{
		*offset = Vec2iNew(ofpic->dx, ofpic->dy);
	}
	return pic;
}
Exemple #2
0
static void LoadGunSpawners(CArray *classes, const CArray *guns)
{
	for (int i = 0; i < (int)guns->size; i++)
	{
		const GunDescription *g = CArrayGet(guns, i);
		if (!g->IsRealGun)
		{
			continue;
		}
		MapObject m;
		memset(&m, 0, sizeof m);
		char buf[256];
		sprintf(buf, "%s spawner", g->name);
		CSTRDUP(m.Name, buf);
		m.Normal.Pic = PicManagerGetPic(&gPicManager, "spawn_pad");
		m.Normal.Offset = Vec2iNew(
			-m.Normal.Pic->size.x / 2,
			TILE_HEIGHT / 2 - m.Normal.Pic->size.y);
		m.Size = Vec2iNew(TILE_WIDTH, TILE_HEIGHT);
		m.Health = 0;
		m.Type = MAP_OBJECT_TYPE_PICKUP_SPAWNER;
		sprintf(buf, "gun_%s", g->name);
		m.u.PickupClass = StrPickupClass(buf);
		CArrayPushBack(classes, &m);
	}
}
Exemple #3
0
static void DrawEditorTiles(DrawBuffer *b, Vec2i offset)
{
	Vec2i pos;
	Tile *tile = &b->tiles[0][0];
	pos.y = b->dy + offset.y;
	for (int y = 0; y < Y_TILES; y++, pos.y += TILE_HEIGHT)
	{
		pos.x = b->dx + offset.x;
		for (int x = 0; x < b->Size.x; x++, tile++, pos.x += TILE_WIDTH)
		{
			if (gMission.missionData->Type == MAPTYPE_STATIC)
			{
				Vec2i start = gMission.missionData->u.Static.Start;
				if (!Vec2iEqual(start, Vec2iZero()) &&
					Vec2iEqual(start, Vec2iNew(x + b->xStart, y + b->yStart)))
				{
					// mission start
					BlitMasked(
						&gGraphicsDevice,
						PicManagerGetPic(&gPicManager, "editor/start"),
						pos, colorWhite, 1);
				}
			}
		}
		tile += X_TILES - b->Size.x;
	}
}
Exemple #4
0
void LoadPic(const Pic **value, json_t *node, const char *name)
{
	if (json_find_first_label(node, name))
	{
		char *tmp = GetString(node, name);
		*value = PicManagerGetPic(&gPicManager, tmp);
		CFREE(tmp);
	}
}
Exemple #5
0
static void LoadAmmo(Ammo *a, json_t *node)
{
	a->Name = GetString(node, "Name");
	char *tmp;
	tmp = GetString(node, "Pic");
	a->Pic = PicManagerGetPic(&gPicManager, tmp);
	CFREE(tmp);
	a->Sound = GetString(node, "Sound");
	LoadInt(&a->Amount, node, "Amount");
	LoadInt(&a->Max, node, "Max");
}
Exemple #6
0
static void LoadOldPic(
	PicManager *pm, const char *name, const TOffsetPic *pic)
{
	// Don't use old pics if new ones are available
	if (PicManagerGetPic(pm, name) != NULL)
	{
		return;
	}
	NamedPic p;
	CSTRDUP(p.name, name);
	const Pic *original = PicManagerGetFromOld(pm, pic->picIndex);
	PicCopy(&p.pic, original);
	CArrayPushBack(&pm->pics, &p);
}
Exemple #7
0
void LoadPic(
	const Pic **value, json_t *node, const char *name, const char *oldPicName)
{
	if (json_find_first_label(node, name))
	{
		char *tmp = GetString(node, name);
		*value = PicManagerGetPic(&gPicManager, tmp);
		CFREE(tmp);
	}
	if ((*value == NULL || ConfigGetBool(&gConfig, "Graphics.OriginalPics")) &&
		json_find_first_label(node, oldPicName))
	{
		int oldPic;
		LoadInt(&oldPic, node, oldPicName);
		*value = PicManagerGetFromOld(&gPicManager, oldPic);
	}
}
Exemple #8
0
static void SetupSpawner(
	MapObject *m, const char *spawnerName, const char *pickupClassName)
{
	memset(m, 0, sizeof *m);
	CSTRDUP(m->Name, spawnerName);
	m->Pic.Type = PICTYPE_NORMAL;
	m->Pic.u.Pic = PicManagerGetPic(&gPicManager, "spawn_pad");
	m->Pic.UseMask = true;
	m->Pic.u1.Mask = colorWhite;
	const Vec2i size = CPicGetSize(&m->Pic);
	m->Offset = Vec2iNew(-size.x / 2, TILE_HEIGHT / 2 - size.y);
	m->Size = TILE_SIZE;
	m->Health = 0;
	m->DrawLast = true;
	m->Type = MAP_OBJECT_TYPE_PICKUP_SPAWNER;
	m->u.PickupClass = StrPickupClass(pickupClassName);
}
Exemple #9
0
static void LoadAmmoSpawners(CArray *classes, const CArray *ammo)
{
	for (int i = 0; i < (int)ammo->size; i++)
	{
		const Ammo *a = CArrayGet(ammo, i);
		MapObject m;
		memset(&m, 0, sizeof m);
		char buf[256];
		sprintf(buf, "%s spawner", a->Name);
		CSTRDUP(m.Name, buf);
		m.Normal.Pic = PicManagerGetPic(&gPicManager, "spawn_pad");
		m.Normal.Offset = Vec2iNew(
			-m.Normal.Pic->size.x / 2,
			TILE_HEIGHT / 2 - m.Normal.Pic->size.y);
		m.Size = Vec2iNew(TILE_WIDTH, TILE_HEIGHT);
		m.Health = 0;
		m.Type = MAP_OBJECT_TYPE_PICKUP_SPAWNER;
		sprintf(buf, "ammo_%s", a->Name);
		m.u.PickupClass = StrPickupClass(buf);
		CArrayPushBack(classes, &m);
	}
}
Exemple #10
0
Pic *PicManagerGet(PicManager *pm, const char *name, const int oldIdx)
{
	Pic *pic;
	if (!name || name[0] == '\0' || gConfig.Graphics.OriginalPics)
	{
		goto defaultPic;
	}
	pic = PicManagerGetPic(pm, name);
	if (!pic)
	{
		goto defaultPic;
	}
	return pic;

defaultPic:
	pic = PicManagerGetFromOld(pm, oldIdx);
	CASSERT(pic != NULL, "Cannot find pic");
	if (pic == NULL)
	{
		pic = PicManagerGetFromOld(pm, PIC_UZIBULLET);
	}
	return pic;
}
Exemple #11
0
static void DrawCompassArrow(
	GraphicsDevice *g, Rect2i r, Vec2i pos, Vec2i playerPos, color_t mask,
	const char *label)
{
	Vec2i compassV = Vec2iMinus(pos, playerPos);
	// Don't draw if objective is on screen
	if (abs(pos.x - playerPos.x) < r.Size.x / 2 &&
		abs(pos.y - playerPos.y) < r.Size.y / 2)
	{
		return;
	}
	Vec2i textPos = Vec2iZero();
	// Find which edge of screen is the best
	bool hasDrawn = false;
	if (compassV.x != 0)
	{
		double sx = r.Size.x / 2.0 / compassV.x;
		int yInt = (int)floor(fabs(sx) * compassV.y + 0.5);
		if (yInt >= -r.Size.y / 2 && yInt <= r.Size.y / 2)
		{
			// Intercepts either left or right side
			hasDrawn = true;
			if (compassV.x > 0)
			{
				// right edge
				textPos = Vec2iNew(
					r.Pos.x + r.Size.x, r.Pos.y + r.Size.y / 2 + yInt);
				const Pic *p = PicManagerGetPic(&gPicManager, "arrow_right");
				Vec2i drawPos = Vec2iNew(
					textPos.x - p->size.x, textPos.y - p->size.y / 2);
				BlitMasked(g, p, drawPos, mask, true);
			}
			else if (compassV.x < 0)
			{
				// left edge
				textPos = Vec2iNew(r.Pos.x, r.Pos.y + r.Size.y / 2 + yInt);
				const Pic *p = PicManagerGetPic(&gPicManager, "arrow_left");
				Vec2i drawPos = Vec2iNew(textPos.x, textPos.y - p->size.y / 2);
				BlitMasked(g, p, drawPos, mask, true);
			}
		}
	}
	if (!hasDrawn && compassV.y != 0)
	{
		double sy = r.Size.y / 2.0 / compassV.y;
		int xInt = (int)floor(fabs(sy) * compassV.x + 0.5);
		if (xInt >= -r.Size.x / 2 && xInt <= r.Size.x / 2)
		{
			// Intercepts either top or bottom side
			if (compassV.y > 0)
			{
				// bottom edge
				textPos = Vec2iNew(
					r.Pos.x + r.Size.x / 2 + xInt, r.Pos.y + r.Size.y);
				const Pic *p = PicManagerGetPic(&gPicManager, "arrow_down");
				Vec2i drawPos = Vec2iNew(
					textPos.x - p->size.x / 2, textPos.y - p->size.y);
				BlitMasked(g, p, drawPos, mask, true);
			}
			else if (compassV.y < 0)
			{
				// top edge
				textPos = Vec2iNew(r.Pos.x + r.Size.x / 2 + xInt, r.Pos.y);
				const Pic *p = PicManagerGetPic(&gPicManager, "arrow_up");
				Vec2i drawPos = Vec2iNew(textPos.x - p->size.x / 2, textPos.y);
				BlitMasked(g, p, drawPos, mask, true);
			}
		}
	}
	if (label && strlen(label) > 0)
	{
		Vec2i textSize = FontStrSize(label);
		// Center the text around the target position
		textPos.x -= textSize.x / 2;
		textPos.y -= textSize.y / 2;
		// Make sure the text is inside the screen
		int padding = 8;
		textPos.x = MAX(textPos.x, r.Pos.x + padding);
		textPos.x = MIN(textPos.x, r.Pos.x + r.Size.x - textSize.x - padding);
		textPos.y = MAX(textPos.y, r.Pos.y + padding);
		textPos.y = MIN(textPos.y, r.Pos.y + r.Size.y - textSize.y - padding);
		FontStrMask(label, textPos, mask);
	}
}
UIObject *CreateStaticMapObjs(
	Vec2i pos, CampaignOptions *co, EditorBrush *brush)
{
	int x = pos.x;
	const int th = FontH();
	UIObject *c = UIObjectCreate(UITYPE_NONE, 0, Vec2iZero(), Vec2iZero());
	UIObject *o2;
	// Check whether the map type matches, and set visibility
	c->CheckVisible = MissionCheckTypeStatic;
	c->Data = co;

	UIObject *o = UIObjectCreate(UITYPE_BUTTON, 0, Vec2iZero(), Vec2iZero());
	o->Data = brush;
	o->OnFocusFunc = ActivateBrush;
	o->OnUnfocusFunc = DeactivateBrush;
	o->ChangesData = false;
	o2 = UIObjectCopy(o);
	UIButtonSetPic(o2, PicManagerGetPic(&gPicManager, "editor/pencil"));
	o2->u.Button.IsDownFunc = BrushIsBrushTypePoint;
	o2->ChangeFunc = BrushSetBrushTypePoint;
	CSTRDUP(o2->Tooltip, "Point");
	o2->Pos = pos;
	UIObjectAddChild(c, o2);
	pos.x += o2->Size.x;
	o2 = UIObjectCopy(o);
	UIButtonSetPic(o2, PicManagerGetPic(&gPicManager, "editor/line"));
	o2->u.Button.IsDownFunc = BrushIsBrushTypeLine;
	o2->ChangeFunc = BrushSetBrushTypeLine;
	CSTRDUP(o2->Tooltip, "Line");
	o2->Pos = pos;
	UIObjectAddChild(c, o2);
	pos.x += o2->Size.x;
	o2 = UIObjectCopy(o);
	UIButtonSetPic(o2, PicManagerGetPic(&gPicManager, "editor/box"));
	o2->u.Button.IsDownFunc = BrushIsBrushTypeBox;
	o2->ChangeFunc = BrushSetBrushTypeBox;
	CSTRDUP(o2->Tooltip, "Box");
	o2->Pos = pos;
	UIObjectAddChild(c, o2);
	pos.x += o2->Size.x;
	o2 = UIObjectCopy(o);
	UIButtonSetPic(o2, PicManagerGetPic(&gPicManager, "editor/box_filled"));
	o2->u.Button.IsDownFunc = BrushIsBrushTypeBoxFilled;
	o2->ChangeFunc = BrushSetBrushTypeBoxFilled;
	CSTRDUP(o2->Tooltip, "Box filled");
	o2->Pos = pos;
	UIObjectAddChild(c, o2);
	pos.x += o2->Size.x;
	o2 = UIObjectCopy(o);
	UIButtonSetPic(o2, PicManagerGetPic(&gPicManager, "editor/room"));
	o2->u.Button.IsDownFunc = BrushIsBrushTypeRoom;
	o2->ChangeFunc = BrushSetBrushTypeRoom;
	CSTRDUP(o2->Tooltip, "Room");
	o2->Pos = pos;
	UIObjectAddChild(c, o2);
	pos.x += o2->Size.x;
	o2 = UIObjectCopy(o);
	UIButtonSetPic(o2, PicManagerGetPic(&gPicManager, "editor/select"));
	o2->u.Button.IsDownFunc = BrushIsBrushTypeSelect;
	o2->ChangeFunc = BrushSetBrushTypeSelect;
	CSTRDUP(o2->Tooltip, "Select and move");
	o2->Pos = pos;
	UIObjectAddChild(c, o2);
	pos.x += o2->Size.x;
	o2 = UIObjectCopy(o);
	UIButtonSetPic(o2, PicManagerGetPic(&gPicManager, "editor/bucket"));
	o2->u.Button.IsDownFunc = BrushIsBrushTypeFill;
	o2->ChangeFunc = BrushSetBrushTypeFill;
	o2->Pos = pos;
	UIObjectAddChild(c, o2);
	pos.x += o2->Size.x;
	o2 = UIObjectCopy(o);
	UIButtonSetPic(o2, PicManagerGetPic(&gPicManager, "editor/add"));
	o2->u.Button.IsDownFunc = BrushIsBrushTypeAddItem;
	CSTRDUP(o2->Tooltip, "Add items\nRight click to remove");
	o2->Pos = pos;
	o2->OnFocusFunc = NULL;
	o2->OnUnfocusFunc = NULL;
	UIObjectAddChild(o2, CreateAddItemObjs(o2->Size, brush, co));
	UIObjectAddChild(c, o2);
	pos.x += o2->Size.x;
	o2 = UIObjectCopy(o);
	UIButtonSetPic(o2, PicManagerGetPic(&gPicManager, "editor/set_key"));
	o2->u.Button.IsDownFunc = BrushIsBrushTypeSetKey;
	CSTRDUP(o2->Tooltip, "Set key required for door");
	o2->Pos = pos;
	o2->OnFocusFunc = NULL;
	o2->OnUnfocusFunc = NULL;
	UIObjectAddChild(o2, CreateSetKeyObjs(o2->Size, brush));
	UIObjectAddChild(c, o2);
	pos.x += o2->Size.x;
	o2 = UIObjectCopy(o);
	UIButtonSetPic(o2, PicManagerGetPic(&gPicManager, "editor/set_exit"));
	o2->u.Button.IsDownFunc = BrushIsBrushTypeSetExit;
	o2->ChangeFunc = BrushSetBrushTypeSetExit;
	CSTRDUP(o2->Tooltip, "Set exit area (box drag)");
	o2->Pos = pos;
	UIObjectAddChild(c, o2);

	UIObjectDestroy(o);
	o = UIObjectCreate(
		UITYPE_LABEL, 0, Vec2iZero(), Vec2iNew(60, th));
	pos.x = x;
	pos.y += o2->Size.y;
	o2 = UIObjectCopy(o);
	o2->u.LabelFunc = BrushGetMainTypeStr;
	o2->Data = brush;
	o2->ChangeFunc = BrushChangeMainType;
	o2->OnFocusFunc = ActivateBrush;
	o2->OnUnfocusFunc = DeactivateBrush;
	CSTRDUP(o2->Tooltip, "Left click to paint the map with this tile type");
	o2->Pos = pos;
	UIObjectAddChild(c, o2);
	pos.x += o2->Size.x;
	o2 = UIObjectCopy(o);
	o2->u.LabelFunc = BrushGetSecondaryTypeStr;
	o2->Data = brush;
	o2->ChangeFunc = BrushChangeSecondaryType;
	o2->OnFocusFunc = ActivateBrush;
	o2->OnUnfocusFunc = DeactivateBrush;
	CSTRDUP(o2->Tooltip, "Right click to paint the map with this tile type");
	o2->Pos = pos;
	UIObjectAddChild(c, o2);

	pos.x = x;
	pos.y += th;
	o2 = UIObjectCopy(o);
	o2->u.LabelFunc = BrushGetSizeStr;
	o2->Data = brush;
	o2->ChangeFunc = BrushChangeSize;
	o2->OnFocusFunc = ActivateBrush;
	o2->OnUnfocusFunc = DeactivateBrush;
	o2->Pos = pos;
	UIObjectAddChild(c, o2);

	UIObjectDestroy(o);
	o = UIObjectCreate(UITYPE_TEXTBOX, 0, Vec2iZero(), Vec2iNew(100, th));
	pos.x = x;
	pos.y += th;
	o2 = UIObjectCopy(o);
	o2->u.Textbox.TextLinkFunc = BrushGetGuideImageStr;
	o2->u.Textbox.MaxLen = sizeof((EditorBrush *)0)->GuideImage - 1;
	o2->Data = brush;
	o2->ChangesData = 0;
	o2->ChangeFunc = BrushLoadGuideImage;
	CSTRDUP(o2->u.Textbox.Hint, "(Tracing guide image)");
	o2->Pos = pos;
	UIObjectAddChild(c, o2);

	UIObjectDestroy(o);
	o = UIObjectCreate(UITYPE_LABEL, 0, Vec2iZero(), Vec2iNew(100, th));
	pos.x = x;
	pos.y += th;
	o2 = UIObjectCopy(o);
	o2->u.LabelFunc = BrushGetGuideImageAlphaStr;
	o2->Data = brush;
	o2->ChangeFunc = BrushChangeGuideImageAlpha;
	o2->Pos = pos;
	UIObjectAddChild(c, o2);

	UIObjectDestroy(o);
	return c;
}
Exemple #13
0
static void LoadBullet(
	BulletClass *b, json_t *node, const BulletClass *defaultBullet)
{
	memset(b, 0, sizeof *b);
	if (defaultBullet != NULL)
	{
		memcpy(b, defaultBullet, sizeof *b);
		if (defaultBullet->HitSound.Object != NULL)
		{
			CSTRDUP(b->HitSound.Object, defaultBullet->HitSound.Object);
		}
		if (defaultBullet->HitSound.Flesh != NULL)
		{
			CSTRDUP(b->HitSound.Flesh, defaultBullet->HitSound.Flesh);
		}
		if (defaultBullet->HitSound.Wall != NULL)
		{
			CSTRDUP(b->HitSound.Wall, defaultBullet->HitSound.Wall);
		}
		// TODO: enable default bullet guns?
		memset(&b->Falling.DropGuns, 0, sizeof b->Falling.DropGuns);
		memset(&b->OutOfRangeGuns, 0, sizeof b->OutOfRangeGuns);
		memset(&b->HitGuns, 0, sizeof b->HitGuns);
		memset(&b->ProximityGuns, 0, sizeof b->ProximityGuns);
	}
	char *tmp;

	LoadStr(&b->Name, node, "Name");
	if (json_find_first_label(node, "Pic"))
	{
		json_t *pic = json_find_first_label(node, "Pic")->child;
		tmp = GetString(pic, "Type");
		b->CPic.Type = StrPicType(tmp);
		CFREE(tmp);
		bool picLoaded = false;
		switch (b->CPic.Type)
		{
		case PICTYPE_NORMAL:
			tmp = GetString(pic, "Pic");
			b->CPic.u.Pic = PicManagerGetPic(&gPicManager, tmp);
			CFREE(tmp);
			picLoaded = b->CPic.u.Pic != NULL;
			break;
		case PICTYPE_DIRECTIONAL:
			tmp = GetString(pic, "Sprites");
			b->CPic.u.Sprites =
				&PicManagerGetSprites(&gPicManager, tmp)->pics;
			CFREE(tmp);
			picLoaded = b->CPic.u.Sprites != NULL;
			break;
		case PICTYPE_ANIMATED:	// fallthrough
		case PICTYPE_ANIMATED_RANDOM:
			tmp = GetString(pic, "Sprites");
			b->CPic.u.Animated.Sprites =
				&PicManagerGetSprites(&gPicManager, tmp)->pics;
			CFREE(tmp);
			LoadInt(&b->CPic.u.Animated.Count, pic, "Count");
			LoadInt(&b->CPic.u.Animated.TicksPerFrame, pic, "TicksPerFrame");
			// Set safe default ticks per frame 1;
			// if 0 then this leads to infinite loop when animating
			b->CPic.u.Animated.TicksPerFrame = MAX(
				b->CPic.u.Animated.TicksPerFrame, 1);
			picLoaded = b->CPic.u.Animated.Sprites != NULL;
			break;
		default:
			CASSERT(false, "unknown pic type");
			break;
		}
		b->CPic.UseMask = true;
		b->CPic.u1.Mask = colorWhite;
		if (json_find_first_label(pic, "Mask"))
		{
			tmp = GetString(pic, "Mask");
			b->CPic.u1.Mask = StrColor(tmp);
			CFREE(tmp);
		}
		else if (json_find_first_label(pic, "Tint"))
		{
			b->CPic.UseMask = false;
			json_t *tint = json_find_first_label(pic, "Tint")->child->child;
			b->CPic.u1.Tint.h = atof(tint->text);
			tint = tint->next;
			b->CPic.u1.Tint.s = atof(tint->text);
			tint = tint->next;
			b->CPic.u1.Tint.v = atof(tint->text);
		}
		if ((json_find_first_label(pic, "OldPic") &&
			ConfigGetBool(&gConfig, "Graphics.OriginalPics")) ||
			!picLoaded)
		{
			int oldPic = PIC_UZIBULLET;
			LoadInt(&oldPic, pic, "OldPic");
			b->CPic.Type = PICTYPE_NORMAL;
			b->CPic.u.Pic = PicManagerGetFromOld(&gPicManager, oldPic);
		}
	}
	LoadVec2i(&b->ShadowSize, node, "ShadowSize");
	LoadInt(&b->Delay, node, "Delay");
	if (json_find_first_label(node, "Speed"))
	{
		LoadInt(&b->SpeedLow, node, "Speed");
		b->SpeedHigh = b->SpeedLow;
	}
	if (json_find_first_label(node, "SpeedLow"))
	{
		LoadInt(&b->SpeedLow, node, "SpeedLow");
	}
	if (json_find_first_label(node, "SpeedHigh"))
	{
		LoadInt(&b->SpeedHigh, node, "SpeedHigh");
	}
	b->SpeedLow = MIN(b->SpeedLow, b->SpeedHigh);
	b->SpeedHigh = MAX(b->SpeedLow, b->SpeedHigh);
	LoadBool(&b->SpeedScale, node, "SpeedScale");
	LoadInt(&b->Friction, node, "Friction");
	if (json_find_first_label(node, "Range"))
	{
		LoadInt(&b->RangeLow, node, "Range");
		b->RangeHigh = b->RangeLow;
	}
	if (json_find_first_label(node, "RangeLow"))
	{
		LoadInt(&b->RangeLow, node, "RangeLow");
	}
	if (json_find_first_label(node, "RangeHigh"))
	{
		LoadInt(&b->RangeHigh, node, "RangeHigh");
	}
	b->RangeLow = MIN(b->RangeLow, b->RangeHigh);
	b->RangeHigh = MAX(b->RangeLow, b->RangeHigh);
	LoadInt(&b->Power, node, "Power");
	LoadVec2i(&b->Size, node, "Size");
	if (json_find_first_label(node, "Special"))
	{
		tmp = GetString(node, "Special");
		b->Special = StrSpecialDamage(tmp);
		CFREE(tmp);
	}
	LoadBool(&b->HurtAlways, node, "HurtAlways");
	LoadBool(&b->Persists, node, "Persists");
	if (json_find_first_label(node, "Spark"))
	{
		tmp = GetString(node, "Spark");
		b->Spark = StrParticleClass(&gParticleClasses, tmp);
		CFREE(tmp);
	}
	if (json_find_first_label(node, "HitSounds"))
	{
		json_t *hitSounds = json_find_first_label(node, "HitSounds")->child;
		CFREE(b->HitSound.Object);
		b->HitSound.Object = NULL;
		LoadStr(&b->HitSound.Object, hitSounds, "Object");
		CFREE(b->HitSound.Flesh);
		b->HitSound.Flesh = NULL;
		LoadStr(&b->HitSound.Flesh, hitSounds, "Flesh");
		CFREE(b->HitSound.Wall);
		b->HitSound.Wall = NULL;
		LoadStr(&b->HitSound.Wall, hitSounds, "Wall");
	}
	LoadBool(&b->WallBounces, node, "WallBounces");
	LoadBool(&b->HitsObjects, node, "HitsObjects");
	if (json_find_first_label(node, "Falling"))
	{
		json_t *falling = json_find_first_label(node, "Falling")->child;
		LoadInt(&b->Falling.GravityFactor, falling, "GravityFactor");
		LoadBool(&b->Falling.FallsDown, falling, "FallsDown");
		LoadBool(&b->Falling.DestroyOnDrop, falling, "DestroyOnDrop");
		LoadBool(&b->Falling.Bounces, falling, "Bounces");
	}
	LoadInt(&b->SeekFactor, node, "SeekFactor");
	LoadBool(&b->Erratic, node, "Erratic");

	b->node = node;
}