feat(jogo): expediente dos mortos, jogo de zumbis em primeira pessoa
CI / audit (push) Successful in 10m48s
CI / verify (push) Successful in 19m2s
CI / deploy (push) Successful in 2m14s
CI / e2e (push) Failing after 36m15s

Etapa 1 inteira (logica com 100% de cobertura, cena 3D, toque, sons, bancada de desempenho, e2e) e a base da etapa 2 (mapa novo no Tiled, texturas, imagens dos itens, sons conferidos, golpe recortado e medido). Pagina /jogo fora do menu e do sitemap ate a medicao no celular.
This commit is contained in:
evandro committed 2026-11-10 19:37:10 -03:00
1 parent c7d09d63d4
commit 67b32fcab3
118 files changed
+16730

No files matched your search

+30
View File
@@ -0,0 +1,30 @@
# Ferramentas do jogo
Programas baixados ficam em `3d/ferramentas/`, fora do git (`/3d/ferramentas/` no `.gitignore`), instalados sem sudo e sem login.
| programa | versão | licença (frase lida e arquivo) | como baixar | comando sem janela |
| --- | --- | --- | --- | --- |
| Tiled (com `tmxrasterizer`) | 1.12.2 | GPL-2.0 (editor) e BSD-2-Clause (`libtiled`, `tmxrasterizer`). Em `COPYING` do pacote: "Tiled src/tiled GPL … tmxrasterizer src/tmxrasterizer BSD 2-clause license … The full text of each license is provided in the files LICENSE.GPL and LICENSE.BSD." Arquivo: `/app/share/licenses/org.mapeditor.Tiled/tiled/COPYING` no Flatpak (o mesmo texto em `3d/ferramentas/tiled/squashfs-root/COPYING` no AppImage). | `flatpak install --user -y flathub org.mapeditor.Tiled`. O AppImage (`3d/ferramentas/tiled/`, das [releases](https://github.com/mapeditor/tiled/releases/tag/v1.12.2)) roda, mas só traz o plugin Qt `xcb` e falha com `QT_QPA_PLATFORM=offscreen`; `xvfb-run` não existe aqui. | `env -u DISPLAY -u WAYLAND_DISPLAY flatpak run --env=QT_QPA_PLATFORM=offscreen --command=tiled org.mapeditor.Tiled --export-map json "$PWD/3d/mapa/escritorio.tmx" "$PWD/3d/mapa/escritorio.json"` e `env -u DISPLAY -u WAYLAND_DISPLAY flatpak run --env=QT_QPA_PLATFORM=offscreen --command=tmxrasterizer org.mapeditor.Tiled "$PWD/3d/mapa/escritorio.tmx" "$PWD/3d/mapa/escritorio.png"` |
| Blender | 5.2.2 LTS (`blender --version`) | GPL. Em `/usr/share/doc/blender/readme.html`: "Blender can be used freely for any purpose, including commercial use and distribution. It's free and open-source software, released under the GNU GPL licence." Bibliotecas de terceiros em `/usr/share/doc/blender/license/license.md`. | Já instalado pelo sistema (pacote `blender` 5.2.2-2 do Manjaro). | `blender -b -P script.py` |
| MPFB (MakeHuman para Blender, extensão) | 2.0.18 (`blender_manifest.toml`; build 20261010) | Código GPL-3.0-or-later, recursos CC0. Em `LICENSE.md` do [repositório](https://github.com/makehumancommunity/mpfb2/blob/master/LICENSE.md): "The MPFB source (as defined per the above) is released under GPLv3." e "These assets have been released under CC0 1.0 Universal."; sobre a saída: "As the assets have been released under CC0, there is no limitation on what you can do with this combined output." O zip da extensão não traz arquivo de licença; o manifesto diz `license = ["SPDX:GPL-3.0-or-later"]`. | `3d/ferramentas/baixados/add-on-mpfb-v2.0.18.zip` de [extensions.blender.org](https://extensions.blender.org/add-ons/mpfb/) (sha256 `c075b3e9…2a2dad`), instalado só na pasta separada: `BLENDER_USER_RESOURCES=$PWD/3d/ferramentas/blender-user blender -b --factory-startup --command extension install-file -r user_default -e 3d/ferramentas/baixados/add-on-mpfb-v2.0.18.zip` | `BLENDER_USER_RESOURCES=$PWD/3d/ferramentas/blender-user blender -b -P 3d/humano.py` (instala os pacotes abaixo na pasta de dados do MPFB se faltarem) |
## Como mudar o mapa
1. Abrir `3d/mapa/escritorio.tmx` no Tiled: `flatpak run org.mapeditor.Tiled 3d/mapa/escritorio.tmx`.
2. Editar as camadas `planta`, `moveis` e `marcas` e salvar.
3. Rodar `bash 3d/mapa.sh`: exporta `3d/mapa/escritorio.json` e `escritorio.png`, escreve `lib/jogo/mapas/escritorio.ts` e monta `public/jogo/mapa.glb` no Blender.
4. Rodar `pnpm test -- lib/jogo/mapas`.
O `.tmx` é a fonte. `bash 3d/mapa.sh --gerar` refaz o `.tmx` pelo `3d/mapa.py` e apaga o que foi editado à mão.
## Pacotes de recursos do MPFB
Baixados de `https://files.makehumancommunity.org/asset_packs/<pacote>/<pacote>_cc0.zip` (o link `files2` da [página](https://static.makehumancommunity.org/assets/assetpacks/index.html) dá o mesmo arquivo, mas a ~10 KB/s) para `3d/ferramentas/baixados/` e extraídos em `3d/ferramentas/blender-user/extensions/.user/user_default/mpfb/data/`. Nenhum zip traz arquivo de licença: a licença lida é o campo `"license"` de cada recurso em `packs/<pacote>.json` (dentro do zip) e o cabeçalho dos arquivos usados. Há cabeçalhos AGPL em alguns arquivos de pacotes marcados CC0 (ex.: `cortu_cargo_pants/cargo_pants.obj`, `blindsaypatten_uniform_skin_texture.mhmat`); esses ficaram de fora.
| pacote | sha256 | recursos usados em `3d/humano.py` | licença lida |
| --- | --- | --- | --- |
| makehuman_system_assets | `b542127a…0a0107` | pele `young_caucasian_male`, olhos `low-poly`, sobrancelha `eyebrow001`, cabelo `short02` (a malha base e o esqueleto `mixamo` vêm dentro da extensão, recursos CC0 pelo `LICENSE.md` acima) | CC0 nos 94 itens de `packs/makehuman_system_assets.json`; cabeçalho dos `.mhmat`/`.mhclo`: "This asset was explicitly released as CC0 in september 2020." |
| shirts01 | `a5a723b0…3bcd3` | `namuhekam_male_polo_shirt` (autor namuhekam) | `"license": "CC0"` em `packs/shirts01.json`; CC0 no cabeçalho do `.mhclo`, `.obj` e `.mhmat` |
| pants01 | `e4e0ec60…ad9` | `toigo_wool_pants` (autora MargaretToigo) | `"license": "CC0"` em `packs/pants01.json`; CC0 no cabeçalho do `.mhclo` e `.obj` |
| shoes01 | `ded3f704…5dba9` | `toigo_ankle_boots_male` (autora MargaretToigo) | `"license": "CC0"` em `packs/shoes01.json`; CC0 no cabeçalho do `.mhclo` e `.obj` |
| skins01, hair01 | `7495ab99…4ad9`, `49445d69…40c9` | baixados, não usados | CC0 no `packs/*.json`, mas com cabeçalhos AGPL em 2 e 10 arquivos |
+333
View File
@@ -0,0 +1,333 @@
"""Prova do MPFB2 (MakeHuman para Blender) por linha de comando: homem adulto magro, vestido, com esqueleto "mixamo".
Mede triângulos e ossos, passa os 6 clipes do zumbi atual (3d/fontes/zumbi, ossos mixamorig:*) para o esqueleto
novo, exporta 3d/out/jogo/humano.glb e fotografa com a luz de zumbi-frente.png e zumbi-perto.png (3d/jogo.py).
Rodar SEMPRE com a pasta de usuário separada (a extensão e os pacotes ficam lá, fora do Blender do Evandro):
BLENDER_USER_RESOURCES=$PWD/3d/ferramentas/blender-user blender -b -P 3d/humano.py
A extensão entra uma vez com (ver 3d/FERRAMENTAS.md):
BLENDER_USER_RESOURCES=… blender -b --factory-startup --command extension install-file -r user_default -e <zip>
"""
import hashlib
import os
import sys
import urllib.request
import bpy
from mathutils import Vector
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from blocos import RAIZ, SAIDA, falhar, ligar_cycles, sair # noqa: E402
try:
from bl_ext.user_default.mpfb.services import AssetService, HumanService, LocationService
except ImportError:
falhar("MPFB não carregou: rode com BLENDER_USER_RESOURCES=3d/ferramentas/blender-user e instale a extensão")
BAIXADOS = os.path.join(RAIZ, "3d", "ferramentas", "baixados")
CLIPES_FBX = os.path.join(RAIZ, "3d", "fontes", "zumbi") # baixados e extraídos por 3d/jogo.py
RENDERS = os.path.join(SAIDA, "jogo")
GLB = os.path.join(RENDERS, "humano.glb")
# pacote -> sha256 do zip (só pacotes CC0; https://static.makehumancommunity.org/assets/assetpacks/index.html)
PACOTES = {
"makehuman_system_assets": "b542127a8e25547c7c29c19f2d1d2adb9a664c80396ecd694095dbc8028a0107",
"shirts01": "a5a723b0e84a109bb190fcfeac7f1de4138d875da3e30fe5b3340eac9f38bcd3",
"pants01": "e4e0ec60db34f279be291a83cfd7b342a7c5cf09bb7676682a5f39f4f6ac4ad9",
"shoes01": "ded3f70428505eabbf1f6d7b5f61196a7366ef20757103d276ad0ed336c35ada",
}
# files2 (o link da página) entrega a ~10 KB/s; files é o mesmo conteúdo (mesmo tamanho e sha256)
URL_PACOTE = "https://files.makehumancommunity.org/asset_packs/{0}/{0}_cc0.zip"
# homem adulto (25 anos), magro, altura média; a raça fica no padrão do MPFB (1/3 de cada)
CORPO = {"gender": 1.0, "age": 0.5, "muscle": 0.5, "weight": 0.3, "proportions": 0.5, "height": 0.5,
"cupsize": 0.5, "firmness": 0.5, "race": {"asian": 0.33, "caucasian": 0.33, "african": 0.33}}
PELE = "skins/young_caucasian_male/young_caucasian_male.mhmat"
# (tipo do MPFB, arquivo na pasta de dados); escolhidos com cabeçalho CC0 nos .mhclo e .obj (cargo_pants.obj é AGPL)
PECAS = [
("Eyes", "eyes/low-poly/low-poly.mhclo"),
("Eyebrows", "eyebrows/eyebrow001/eyebrow001.mhclo"),
("Hair", "hair/short02/short02.mhclo"),
("Clothes", "clothes/namuhekam_male_polo_shirt/namuhekam_male_polo_shirt.mhclo"),
("Clothes", "clothes/toigo_wool_pants/toigo_wool_pants.mhclo"),
("Clothes", "clothes/toigo_ankle_boots_male/toigo_ankle_boots_male.mhclo"), # o mj_cloth_shoes é sapatilha
]
ESQUELETO = "mixamo"
MAX_TRIS = 12_000
QUADRIL = "mixamorig:Hips"
PERNA = ["UpLeg", "Leg", "Foot"]
CLIPES = {
"parado": "Zombie Idle.fbx",
"andar": "Zombie Walk.fbx",
"atacar": "Zombie Attack.fbx",
"morrer": "Zombie Dying.fbx",
"correr": "Zombie Running.fbx",
"reacao": "Zombie Reaction Hit.fbx",
}
def sha256(caminho):
h = hashlib.sha256()
with open(caminho, "rb") as f:
for bloco in iter(lambda: f.read(1 << 20), b""):
h.update(bloco)
return h.hexdigest()
def instalar_pacotes():
dados = LocationService.get_user_data()
for nome, soma in PACOTES.items():
if os.path.exists(os.path.join(dados, "packs", nome + ".json")):
continue
zip_ = os.path.join(BAIXADOS, nome + "_cc0.zip")
if not os.path.exists(zip_):
os.makedirs(BAIXADOS, exist_ok=True)
urllib.request.urlretrieve(URL_PACOTE.format(nome), zip_)
if sha256(zip_) != soma:
falhar(f"{zip_}: sha256 não confere")
erro = AssetService.fix_and_extract_asset_pack_zip(zip_, dados)
if erro:
falhar(f"{nome}: {erro}")
print(f"pacote {nome} instalado em {dados}", flush=True)
AssetService.update_all_asset_lists()
return dados
def triangulos(obj):
malha = obj.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
malha.calc_loop_triangles()
n = len(malha.loop_triangles)
obj.to_mesh_clear()
return n
def criar(dados):
for obj in list(bpy.data.objects):
bpy.data.objects.remove(obj)
corpo = HumanService.create_human(macro_detail_dict=CORPO)
esqueleto = HumanService.add_builtin_rig(corpo, ESQUELETO)
HumanService.set_character_skin(os.path.join(dados, PELE), corpo, skin_type="MAKESKIN", material_instances=False)
pecas = []
for tipo, arquivo in PECAS:
caminho = os.path.join(dados, arquivo)
if not os.path.exists(caminho):
falhar(f"falta {caminho}")
pecas.append(HumanService.add_mhclo_asset(caminho, corpo, asset_type=tipo, subdiv_levels=0))
return esqueleto, corpo, pecas
def aplicar_modificadores(obj):
"""Grava na malha as máscaras (ajudantes e faces do corpo sob a roupa) e tira subdivisões; só fica o Armature."""
bpy.context.view_layer.objects.active = obj
if obj.data.shape_keys:
obj.shape_key_add(name="_mistura", from_mix=True)
for chave in list(obj.data.shape_keys.key_blocks)[:-1]:
obj.shape_key_remove(chave)
obj.shape_key_remove(obj.data.shape_keys.key_blocks[0])
for mod in list(obj.modifiers):
if mod.type == "ARMATURE":
continue
if mod.type == "SUBSURF":
obj.modifiers.remove(mod)
continue
with bpy.context.temp_override(object=obj, active_object=obj):
bpy.ops.object.modifier_apply(modifier=mod.name)
def reduzir(malhas, alvo):
"""Colapso de arestas com a mesma razão em todas as malhas (antes do Armature: pesos interpolados)."""
total = sum(triangulos(m) for m in malhas)
razao = alvo / total
for _ in range(4):
for m in malhas:
mod = m.modifiers.new("reduzir", "DECIMATE")
mod.ratio = min(1.0, razao)
mod.use_collapse_triangulate = True
m.modifiers.move(len(m.modifiers) - 1, 0)
with bpy.context.temp_override(object=m, active_object=m):
bpy.ops.object.modifier_apply(modifier=mod.name)
total = sum(triangulos(m) for m in malhas)
if total <= MAX_TRIS:
return total
razao = alvo / total
falhar(f"redução parou em {total} triângulos")
def comprimento_perna(esqueleto):
ossos = esqueleto.pose.bones
juntas = [esqueleto.matrix_world @ ossos["mixamorig:Left" + n].head for n in PERNA]
return sum((b - a).length for a, b in zip(juntas, juntas[1:]))
def contato(esqueleto):
ossos = esqueleto.pose.bones
pes = [f"mixamorig:{lado}{osso}" for lado in ("Left", "Right") for osso in ("Foot", "ToeBase")]
return min((esqueleto.matrix_world @ ossos[n].head).z - ossos[n].bone.head_local.z for n in pes)
def quadro_final(acao):
fim = int(acao.frame_range[0])
for curva in acao.layers[0].strips[0].channelbags[0].fcurves:
pontos = curva.keyframe_points
for anterior, ponto in zip(pontos, pontos[1:]):
if abs(ponto.co[1] - anterior.co[1]) > 1e-4:
fim = max(fim, round(ponto.co[0]))
return fim
def alinhar_rolagem(esqueleto, fonte):
"""Gira cada osso do MPFB no próprio eixo até o Z dele apontar como o do osso do Mixamo no repouso.
A cópia de rotação é em espaço de mundo: com a rolagem igual, o osso novo fica com a direção e a torção do
osso do clipe, mesmo com o repouso diferente (MPFB em A, Mixamo em T). A malha não muda: só o eixo do osso gira.
"""
bpy.context.view_layer.objects.active = esqueleto
bpy.ops.object.mode_set(mode="EDIT")
para_local = esqueleto.matrix_world.inverted().to_3x3()
for osso in esqueleto.data.edit_bones:
if osso.name in fonte.data.bones:
z = (fonte.matrix_world.to_3x3() @ fonte.data.bones[osso.name].matrix_local.to_3x3()).col[2]
osso.align_roll(para_local @ z)
bpy.ops.object.mode_set(mode="OBJECT")
def transferir(esqueleto, nome_clipe, arquivo, alinhar):
"""Como em 3d/jogo.py: rotação de mundo de todos os ossos e posição só do quadril, escalada pela perna e com o pé
de apoio no chão no primeiro quadro; assa a ação."""
antes = set(bpy.data.objects)
bpy.ops.import_scene.fbx(filepath=os.path.join(CLIPES_FBX, arquivo))
novos = [o for o in bpy.data.objects if o not in antes]
fonte = next(o for o in novos if o.type == "ARMATURE")
if alinhar:
alinhar_rolagem(esqueleto, fonte)
acao_fonte = fonte.animation_data.action
inicio, fim = int(acao_fonte.frame_range[0]), quadro_final(acao_fonte)
bpy.context.scene.frame_set(inicio)
fonte.scale *= comprimento_perna(esqueleto) / comprimento_perna(fonte)
sem_par = []
for pb in esqueleto.pose.bones:
if pb.name not in fonte.pose.bones:
sem_par.append(pb.name)
continue
c = pb.constraints.new("COPY_ROTATION")
c.target, c.subtarget = fonte, pb.name
if pb.name == QUADRIL:
c = pb.constraints.new("COPY_LOCATION")
c.target, c.subtarget = fonte, pb.name
if sem_par:
falhar(f"ossos do MPFB sem par no clipe: {sem_par}")
bpy.context.scene.frame_set(inicio)
fonte.location.z -= contato(esqueleto)
bpy.context.view_layer.objects.active = esqueleto
bpy.ops.object.mode_set(mode="POSE")
bpy.ops.pose.select_all(action="SELECT")
bpy.ops.nla.bake(frame_start=inicio, frame_end=fim, only_selected=True, visual_keying=True,
clear_constraints=True, use_current_action=False, bake_types={"POSE"},
channel_types={"LOCATION", "ROTATION"})
bpy.ops.object.mode_set(mode="OBJECT")
acao = esqueleto.animation_data.action
acao.name = nome_clipe
acao.use_fake_user = True
curvas = acao.layers[0].strips[0].channelbags[0].fcurves
for c in [c for c in curvas if c.data_path.endswith(".location") and f'["{QUADRIL}"]' not in c.data_path]:
curvas.remove(c)
ossos_fonte = fonte.data.bones.keys()
for o in novos:
bpy.data.objects.remove(o)
bpy.data.actions.remove(acao_fonte)
return ossos_fonte, inicio, fim
def exportar(esqueleto, malhas):
os.makedirs(RENDERS, exist_ok=True)
bpy.ops.object.select_all(action="DESELECT")
for o in [esqueleto, *malhas]:
o.select_set(True)
esqueleto.animation_data.action = None
for acao in [a for a in bpy.data.actions if a.name not in CLIPES]:
bpy.data.actions.remove(acao)
bpy.context.scene.frame_set(1)
bpy.ops.export_scene.gltf(filepath=GLB, export_format="GLB", use_selection=True, export_yup=True,
export_apply=False, export_cameras=False, export_lights=False, export_morph=False,
export_tangents=False, export_image_format="JPEG", export_jpeg_quality=80,
export_animations=True, export_animation_mode="ACTIONS",
export_anim_slide_to_zero=True, export_force_sampling=False,
export_optimize_animation_size=True)
def renderizar():
"""Reabre o GLB e fotografa com a luz de renderizar() em 3d/jogo.py: parado no quadro 30 (frente e perto) e
andando no meio do clipe."""
bpy.ops.wm.read_homefile(use_empty=True)
cena = bpy.context.scene
bpy.ops.import_scene.gltf(filepath=GLB)
esqueleto = next(o for o in cena.objects if o.type == "ARMATURE")
malhas = [o for o in cena.objects if o.type == "MESH" and o.parent == esqueleto] # sem a esfera do importador
pontos = [o.matrix_world @ v.co for o in malhas for v in o.data.vertices]
pe, topo = min(p.z for p in pontos), max(p.z for p in pontos)
mundo = bpy.data.worlds.new("neutro")
mundo.use_nodes = True
mundo.node_tree.nodes["Background"].inputs["Color"].default_value = (0.5, 0.5, 0.5, 1)
mundo.node_tree.nodes["Background"].inputs["Strength"].default_value = 0.6
cena.world = mundo
luz = bpy.data.objects.new("principal", bpy.data.lights.new("principal", "AREA"))
luz.data.energy, luz.data.size = 300, 2
luz.location = (1.5, -3, 3)
luz.rotation_euler = (Vector((0, 0, 1.2)) - luz.location).to_track_quat("-Z", "Y").to_euler()
cena.collection.objects.link(luz)
camera = bpy.data.objects.new("camera", bpy.data.cameras.new("camera"))
cena.collection.objects.link(camera)
cena.camera = camera
ligar_cycles(cena)
acoes = {a.name.split("_")[0]: a for a in bpy.data.actions}
for nome, clipe, quadro, alvo, distancia in (("frente", "parado", 30, 0.9, 3.2), ("perto", "parado", 30, 1.4, 1.5),
("andando", "andar", None, 0.9, 3.2)):
acao = next(a for n, a in acoes.items() if n.startswith(clipe))
esqueleto.animation_data.action = acao
cena.frame_set(quadro or int(sum(acao.frame_range) / 2))
camera.location = (0.3 * distancia / 4, -distancia, alvo + 0.1)
camera.rotation_euler = (Vector((0, 0, alvo)) - camera.location).to_track_quat("-Z", "Y").to_euler()
sair(cena, os.path.join(RENDERS, f"humano-{nome}.png"), (800, 800), "PNG", "RGB", 64, False)
return pe, topo
def main():
dados = instalar_pacotes()
esqueleto, corpo, pecas = criar(dados)
roupas = [p for p, (tipo, _) in zip(pecas, PECAS) if tipo == "Clothes"]
t_corpo_visivel = triangulos(corpo)
for mod in corpo.modifiers: # mede o corpo sem as máscaras de roupa ("Delete" do MPFB)
if mod.type == "MASK" and mod.vertex_group != "body":
mod.show_viewport = False
t_corpo = triangulos(corpo)
for mod in corpo.modifiers:
mod.show_viewport = True
t_roupa = sum(triangulos(r) for r in roupas)
t_resto = sum(triangulos(p) for p in pecas if p not in roupas)
malhas = [corpo, *pecas]
t_total = t_corpo + t_roupa + t_resto
print(f"triângulos: corpo {t_corpo} (sem as faces sob a roupa: {t_corpo_visivel}), roupa {t_roupa}, "
f"olhos+sobrancelha+cabelo {t_resto}, total {t_total}", flush=True)
for m in malhas:
aplicar_modificadores(m)
t_escondido = sum(triangulos(m) for m in malhas)
tris = reduzir(malhas, MAX_TRIS * 0.97)
print(f"triângulos: total sem as faces escondidas {t_escondido}, reduzido {tris}", flush=True)
for m in malhas:
print(f" {m.name}: {triangulos(m)}", flush=True)
ossos = [b.name for b in esqueleto.data.bones]
print(f"ossos ({len(ossos)}): {' '.join(ossos)}", flush=True)
for i, (nome, arquivo) in enumerate(CLIPES.items()):
ossos_clipe, inicio, fim = transferir(esqueleto, nome, arquivo, alinhar=i == 0)
print(f"clipe {nome} <- {arquivo} quadros {inicio}-{fim}; ossos do clipe sem par no MPFB: "
f"{sorted(set(ossos_clipe) - set(ossos))}", flush=True)
exportar(esqueleto, malhas)
print(f"humano.glb {os.path.getsize(GLB)} bytes", flush=True)
pe, topo = renderizar()
print(f"repouso: pés em {pe:.3f} m, altura {topo - pe:.3f} m", flush=True)
print(f"OK humano tris={tris} ossos={len(ossos)}", flush=True)
if __name__ == "__main__":
main()
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# Refaz public/jogo/itens.webp (atlas 2x2 de 512x512) a partir de 3d/itens/*.png.
set -euo pipefail
cd "$(dirname "$0")/.."
FUZZ="${FUZZ:-6%}"
Q="${Q:-90}"
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
OUT=public/jogo/itens.webp
CONF=/home/evandro/tmp/jogo
mkdir -p "$CONF"
for n in municao kit cafe bateria; do
# fundo por preenchimento a partir dos 4 cantos (sem limiar global)
magick "3d/itens/$n.png" -alpha set -fuzz "$FUZZ" -fill none \
-draw "alpha 0,0 floodfill" -draw "alpha 1023,0 floodfill" \
-draw "alpha 0,1023 floodfill" -draw "alpha 1023,1023 floodfill" \
-channel A -morphology Erode Disk:1 -blur 0x0.7 +channel \
-trim +repage \
-bordercolor none -background none -gravity center \
-resize 215x215 \
-extent 256x256 "$TMP/$n.png"
done
magick -size 512x512 xc:none \
"$TMP/municao.png" -geometry +0+0 -composite \
"$TMP/kit.png" -geometry +256+0 -composite \
"$TMP/cafe.png" -geometry +0+256 -composite \
"$TMP/bateria.png" -geometry +256+256 -composite \
-define webp:alpha-quality=100 -define webp:method=6 -quality "$Q" "$OUT"
magick -size 512x512 xc:"#808080" "$OUT" -composite "$CONF/itens-atlas-cinza.png"
magick -size 512x512 xc:"#d00000" "$OUT" -composite "$CONF/itens-atlas-vermelho.png"
ls -l "$OUT"
Binary file not shown.

After

Width:  |  Height:  |  Size: 570 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 440 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 803 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

+1094
View File
File diff suppressed because it is too large. Load diff
+355
View File
@@ -0,0 +1,355 @@
"""Mapa do escritório do jogo em /jogo: gera a primeira versão do .tmx do Tiled e converte o JSON exportado para a grade da lógica.
A fonte do mapa é o 3d/mapa/escritorio.tmx, editado à mão no Tiled. Rodar da raiz do repositório (o 3d/mapa.sh faz a cadeia toda):
python3 3d/mapa.py gerar sobrescreve 3d/mapa/escritorio.tmx e 3d/mapa/ladrilhos.png pela semente, apagando as edições
feitas no Tiled; só roda sob `bash 3d/mapa.sh --gerar`
python3 3d/mapa.py converter lê 3d/mapa/escritorio.json (exportado pelo Tiled) e escreve lib/jogo/mapas/escritorio.ts
Coordenadas em células de 1 m: x cresce para a direita (leste), z para baixo (sul) no desenho; retângulos inclusivos.
"""
import json
import random
import struct
import sys
import zlib
from pathlib import Path
from xml.sax.saxutils import escape, quoteattr
SEMENTE = 20261110
LARGURA, ALTURA, LADO = 44, 32, 32
RAIZ = Path(__file__).resolve().parent.parent
PASTA = RAIZ / "3d" / "mapa"
SAIDA_TS = RAIZ / "lib" / "jogo" / "mapas" / "escritorio.ts"
# nome, cor RGB, grade da lógica ("#" bloqueia passo e visão, "M" só o passo, "" é enfeite), cobre a célula inteira
LADRILHOS = [
("parede", (55, 58, 70), "#", True),
("chão", (214, 206, 188), ".", True),
("mesa", (176, 120, 64), "M", True),
("cadeira", (40, 110, 200), "", False),
("armário", (110, 140, 110), "#", True),
("estante de servidor", (0, 150, 140), "#", True),
("sofá", (190, 50, 60), "M", True),
("bancada", (230, 180, 40), "M", True),
("planta de vaso", (60, 160, 60), "", False),
("bebedouro", (120, 200, 240), "", False),
]
GID = {nome: i + 1 for i, (nome, *_resto) in enumerate(LADRILHOS)}
PAREDES = [
# borda
(0, 0, 43, 0), (0, 31, 43, 31), (0, 0, 0, 31), (43, 0, 43, 31),
# faixas de salas e anel do corredor
(0, 7, 43, 7), (0, 24, 43, 24), (9, 7, 9, 24), (34, 7, 34, 24),
(12, 10, 31, 10), (12, 21, 31, 21), (12, 10, 12, 21), (31, 10, 31, 21),
# divisórias
(15, 0, 15, 7), (28, 0, 28, 7), (0, 16, 9, 16), (34, 16, 43, 16), (17, 24, 17, 31), (24, 24, 24, 31),
# poços dos elevadores, caixa da escada, cabines dos banheiros
(1, 29, 3, 30), (6, 29, 8, 30), (21, 27, 23, 30), (1, 17, 2, 21),
]
AMBIENTES = [
("recepção", 1, 1, 14, 6),
("sala de reunião", 16, 1, 27, 6),
("diretoria", 29, 1, 42, 6),
("copa", 1, 8, 8, 15),
("banheiros", 1, 17, 8, 23),
("área de mesas", 13, 11, 30, 20),
("sala do servidor", 35, 8, 42, 15),
("almoxarifado", 35, 17, 42, 23),
("hall dos elevadores", 1, 25, 16, 30),
("escada", 18, 25, 23, 30),
("área de serviço", 25, 25, 42, 30),
]
# trechos do anel, só para espalhar luminárias
CORREDOR = [(10, 8, 33, 9), (10, 22, 33, 23), (10, 10, 11, 21), (32, 10, 33, 21)]
PORTAS = [
("recepção – copa", 4, 7, 5, 7),
("recepção – corredor", 12, 7, 13, 7),
("sala de reunião – corredor", 18, 7, 19, 7),
("sala de reunião – corredor", 24, 7, 25, 7),
("sala de reunião – diretoria", 28, 3, 28, 4),
("diretoria – corredor", 30, 7, 31, 7),
("copa – corredor", 9, 11, 9, 12),
("banheiros – corredor", 9, 19, 9, 20),
("banheiros – hall dos elevadores", 4, 24, 5, 24),
("hall dos elevadores – corredor", 13, 24, 14, 24),
("hall dos elevadores – escada", 17, 27, 17, 28),
("escada – corredor", 20, 24, 21, 24),
("área de serviço – corredor", 28, 24, 29, 24),
("área de serviço – almoxarifado", 38, 24, 39, 24),
("almoxarifado – corredor", 34, 19, 34, 20),
("sala do servidor – almoxarifado", 38, 16, 39, 16),
("sala do servidor – corredor", 34, 11, 34, 12),
("área de mesas – corredor norte", 21, 10, 22, 10),
("área de mesas – corredor norte", 28, 10, 29, 10),
("área de mesas – corredor sul", 21, 21, 22, 21),
("área de mesas – corredor oeste", 12, 15, 12, 16),
("área de mesas – corredor leste", 31, 15, 31, 16),
]
ILHAS = [(15, 13, 19, 14), (23, 13, 27, 14), (15, 17, 19, 18), (23, 17, 27, 18)]
MOVEIS = [
# recepção
("sofá", 3, 1, 5, 1), ("sofá", 1, 3, 1, 6), ("bancada", 8, 3, 11, 3),
("cadeira", 9, 2, 9, 2), ("cadeira", 10, 2, 10, 2), ("planta de vaso", 1, 1, 1, 1), ("bebedouro", 14, 1, 14, 1),
# sala de reunião
("mesa", 18, 3, 25, 4), ("cadeira", 17, 3, 17, 3), ("cadeira", 26, 4, 26, 4),
("planta de vaso", 16, 1, 16, 1), ("planta de vaso", 27, 1, 27, 1),
# diretoria
("mesa", 35, 3, 38, 4), ("cadeira", 37, 2, 37, 2), ("cadeira", 36, 5, 36, 5), ("cadeira", 38, 5, 38, 5),
("sofá", 29, 1, 31, 1), ("armário", 42, 1, 42, 4), ("planta de vaso", 41, 6, 41, 6),
# copa
("bancada", 1, 11, 1, 14), ("bancada", 1, 15, 4, 15), ("armário", 7, 15, 8, 15), ("mesa", 4, 11, 5, 12),
("cadeira", 3, 11, 3, 11), ("cadeira", 6, 12, 6, 12), ("cadeira", 4, 10, 4, 10), ("cadeira", 5, 13, 5, 13),
("bebedouro", 1, 8, 1, 8),
# banheiros
("bancada", 5, 17, 8, 17), ("planta de vaso", 8, 23, 8, 23),
# hall dos elevadores
("sofá", 11, 30, 14, 30), ("planta de vaso", 1, 25, 1, 25), ("bebedouro", 16, 25, 16, 25),
# área de serviço
("armário", 27, 30, 32, 30), ("bancada", 40, 27, 42, 28), ("bebedouro", 33, 25, 33, 25),
# almoxarifado
("armário", 42, 17, 42, 19), ("armário", 37, 20, 39, 20), ("armário", 35, 23, 36, 23),
# sala do servidor
("estante de servidor", 37, 10, 42, 10), ("estante de servidor", 37, 13, 42, 13),
# área de mesas
*[("mesa", *ilha) for ilha in ILHAS],
("armário", 30, 11, 30, 13), ("planta de vaso", 13, 11, 13, 11), ("planta de vaso", 13, 20, 13, 20),
("planta de vaso", 30, 20, 30, 20), ("bebedouro", 30, 14, 30, 14),
# corredor
("planta de vaso", 10, 8, 10, 8), ("planta de vaso", 33, 8, 33, 8),
("planta de vaso", 10, 23, 10, 23), ("planta de vaso", 33, 23, 33, 23),
]
INICIO = (7, 5)
NASCEDOUROS = [
("elevador 1", 2, 27), ("elevador 2", 7, 27), ("escada", 19, 29), ("banheiros", 6, 21),
("fundos, área de serviço", 26, 29), ("fundos, área de serviço", 41, 30), ("fundos, almoxarifado", 41, 22),
]
# célula de chão e para onde o cartaz olha; a parede fica do lado oposto
CARTAZES = [
(7, 1, "S"), (12, 1, "S"), (21, 1, "S"), (16, 4, "L"), (36, 1, "S"), (8, 14, "O"), (20, 9, "N"),
(10, 18, "L"), (33, 14, "O"), (25, 22, "S"), (20, 11, "S"), (13, 18, "L"), (11, 25, "S"),
]
FACES = {"N": (0, 1), "S": (0, -1), "L": (-1, 0), "O": (1, 0)} # deslocamento até a parede
NOTAS = """Escritório do jogo em /jogo, 1 célula = 1 m. Este arquivo é a fonte do mapa: abra no Tiled (`flatpak run org.mapeditor.Tiled`), edite, salve e rode `bash 3d/mapa.sh`, que exporta o JSON, converte a grade da lógica e monta o 3D. `bash 3d/mapa.sh --gerar` refaz este arquivo pelo 3d/mapa.py e apaga as edições feitas aqui.
Camadas: planta (parede e chão), moveis (a propriedade grade de cada ladrilho diz como entra na lógica: # bloqueia passo e visão, M bloqueia o passo e deixa o tiro passar, vazio é só enfeite), marcas (objetos pelo tipo).
Tipos em marcas: inicio (ponto do jogador), nascedouro (ponto; o nome diz o lugar), ambiente (retângulo com o nome da sala), porta (retângulo sobre as células abertas na parede), luminaria (ponto no teto), cartaz (ponto na face da parede; face = para onde o cartaz olha: N para cima no desenho, S para baixo, L para a direita, O para a esquerda; imagem de 1 a 4)."""
def celulas(x0, z0, x1, z1):
return [(x, z) for z in range(z0, z1 + 1) for x in range(x0, x1 + 1)]
def png(largura, altura, rgba):
"""PNG RGBA 8 bits sem metadados (os mesmos pixels dão os mesmos bytes)."""
linhas = b"".join(b"\0" + rgba[y * largura * 4 : (y + 1) * largura * 4] for y in range(altura))
def bloco(tipo, dados):
return struct.pack(">I", len(dados)) + tipo + dados + struct.pack(">I", zlib.crc32(tipo + dados))
cabecalho = struct.pack(">IIBBBBB", largura, altura, 8, 6, 0, 0, 0)
return b"\x89PNG\r\n\x1a\n" + bloco(b"IHDR", cabecalho) + bloco(b"IDAT", zlib.compress(linhas, 9)) + bloco(b"IEND", b"")
def imagem_ladrilhos():
"""Uma linha de ladrilhos de 32 px: cheios com borda escura; enfeites são um quadrado menor sobre fundo transparente."""
largura = LADO * len(LADRILHOS)
pixels = bytearray(largura * LADO * 4)
for i, (_nome, cor, _grade, cheio) in enumerate(LADRILHOS):
escura = tuple(c // 2 for c in cor)
margem = 0 if cheio else 8
for y in range(margem, LADO - margem):
for x in range(margem, LADO - margem):
borda = min(x - margem, y - margem, LADO - 1 - margem - x, LADO - 1 - margem - y) < 2
p = (y * largura + i * LADO + x) * 4
pixels[p : p + 4] = bytes((*(escura if borda else cor), 255))
return png(largura, LADO, bytes(pixels))
def montar_planta():
planta = [["chão"] * LARGURA for _ in range(ALTURA)]
for parede in PAREDES:
for x, z in celulas(*parede):
planta[z][x] = "parede"
for _nome, *porta in PORTAS:
for x, z in celulas(*porta):
planta[z][x] = "chão"
return planta
def montar_moveis(rng):
moveis = [[None] * LARGURA for _ in range(ALTURA)]
for tipo, *retangulo in MOVEIS:
for x, z in celulas(*retangulo):
moveis[z][x] = tipo
# cadeiras dos dois lados de cada ilha; algumas vagas ficam sem cadeira
for x0, z0, x1, z1 in ILHAS:
for x in range(x0, x1 + 1):
for z in (z0 - 1, z1 + 1):
if rng.random() < 0.8:
moveis[z][x] = "cadeira"
return moveis
def objeto(id_, tipo, nome, x, y, largura=None, altura=None, propriedades=()):
attrs = f'id="{id_}" name={quoteattr(nome)} type="{tipo}" x="{x:g}" y="{y:g}"'
if largura is not None:
attrs += f' width="{largura:g}" height="{altura:g}"'
corpo = []
if propriedades:
corpo.append(" <properties>")
corpo += [f' <property name="{n}" {t}value={quoteattr(str(v))}/>' for n, t, v in propriedades]
corpo.append(" </properties>")
if largura is None:
corpo.append(" <point/>")
return [f" <object {attrs}>", *corpo, " </object>"]
def luminarias(x0, z0, x1, z1):
"""Pontos a cada ~5 m dentro do retângulo, em pixels, arredondados à meia célula."""
largura, altura = x1 - x0 + 1, z1 - z0 + 1
nx, nz = max(1, round(largura / 5)), max(1, round(altura / 5))
return [
(round((x0 + largura * (i + 0.5) / nx) * 2) * LADO // 2, round((z0 + altura * (j + 0.5) / nz) * 2) * LADO // 2)
for j in range(nz)
for i in range(nx)
]
def gerar():
rng = random.Random(SEMENTE)
planta = montar_planta()
moveis = montar_moveis(rng)
marcas = []
total = 0
def novo(*args, **kwargs):
nonlocal total
total += 1
marcas.extend(objeto(total, *args, **kwargs))
novo("inicio", "inicio", (INICIO[0] + 0.5) * LADO, (INICIO[1] + 0.5) * LADO)
for nome, x, z in NASCEDOUROS:
novo("nascedouro", nome, (x + 0.5) * LADO, (z + 0.5) * LADO)
for nome, x0, z0, x1, z1 in AMBIENTES:
novo("ambiente", nome, x0 * LADO, z0 * LADO, (x1 - x0 + 1) * LADO, (z1 - z0 + 1) * LADO)
for nome, x0, z0, x1, z1 in PORTAS:
novo("porta", nome, x0 * LADO, z0 * LADO, (x1 - x0 + 1) * LADO, (z1 - z0 + 1) * LADO)
for retangulo in [r[1:] for r in AMBIENTES] + CORREDOR:
for x, y in luminarias(*retangulo):
novo("luminaria", "luminaria", x, y)
for x, z, face in CARTAZES:
dx, dz = FACES[face]
assert planta[z][x] == "chão" and planta[z + dz][x + dx] == "parede", (x, z, face)
px = (x + 0.5 + dx / 2) * LADO
pz = (z + 0.5 + dz / 2) * LADO
propriedades = [("face", "", face), ("imagem", 'type="int" ', rng.randint(1, 4))]
novo("cartaz", "cartaz", px, pz, propriedades=propriedades)
def csv(grade):
return ",\n".join(",".join(str(GID[c] if c else 0) for c in linha) for linha in grade)
tiles = []
for i, (nome, _cor, grade, _cheio) in enumerate(LADRILHOS):
tiles += [
f' <tile id="{i}">',
" <properties>",
f" <property name=\"nome\" value={quoteattr(nome)}/>",
f' <property name="grade" value="{grade}"/>',
" </properties>",
" </tile>",
]
marcas_txt = "\n".join(marcas)
tmx = "\n".join(
[
'<?xml version="1.0" encoding="UTF-8"?>',
f'<map version="1.10" tiledversion="1.12.2" orientation="orthogonal" renderorder="right-down" width="{LARGURA}" height="{ALTURA}" tilewidth="{LADO}" tileheight="{LADO}" infinite="0" nextlayerid="4" nextobjectid="{total + 1}">',
" <properties>",
f' <property name="notas">{escape(NOTAS)}</property>',
f' <property name="semente" type="int" value="{SEMENTE}"/>',
" </properties>",
f' <tileset firstgid="1" name="ladrilhos" tilewidth="{LADO}" tileheight="{LADO}" tilecount="{len(LADRILHOS)}" columns="{len(LADRILHOS)}">',
f' <image source="ladrilhos.png" width="{LADO * len(LADRILHOS)}" height="{LADO}"/>',
*tiles,
" </tileset>",
f' <layer id="1" name="planta" width="{LARGURA}" height="{ALTURA}">',
' <data encoding="csv">',
csv(planta),
"</data>",
" </layer>",
f' <layer id="2" name="moveis" width="{LARGURA}" height="{ALTURA}">',
' <data encoding="csv">',
csv(moveis),
"</data>",
" </layer>",
' <objectgroup id="3" name="marcas">',
marcas_txt,
" </objectgroup>",
"</map>",
"",
]
)
PASTA.mkdir(parents=True, exist_ok=True)
(PASTA / "escritorio.tmx").write_text(tmx, encoding="utf-8")
(PASTA / "ladrilhos.png").write_bytes(imagem_ladrilhos())
def converter():
dados = json.loads((PASTA / "escritorio.json").read_text(encoding="utf-8"))
largura, altura, lado = dados["width"], dados["height"], dados["tilewidth"]
grade_por_gid = {}
for tileset in dados["tilesets"]:
for tile in tileset.get("tiles", []):
propriedades = {p["name"]: p["value"] for p in tile.get("properties", [])}
grade_por_gid[tileset["firstgid"] + tile["id"]] = propriedades["grade"]
camadas = {c["name"]: c for c in dados["layers"]}
linhas = [["#"] * largura for _ in range(altura)]
for nome in ("planta", "moveis"):
for i, gid in enumerate(camadas[nome]["data"]):
grade = grade_por_gid.get(gid & 0x1FFFFFFF, "") # bits altos: ladrilho espelhado ou girado
if grade:
linhas[i // largura][i % largura] = grade
ambientes, portas = [], []
for obj in camadas["marcas"]["objects"]:
x, z = int(obj["x"] // lado), int(obj["y"] // lado)
if obj["type"] in ("inicio", "nascedouro"):
linhas[z][x] = "J" if obj["type"] == "inicio" else "N"
elif obj["type"] in ("ambiente", "porta"):
retangulo = {"x": x, "z": z, "largura": round(obj["width"] / lado), "altura": round(obj["height"] / lado)}
if obj["type"] == "ambiente":
ambientes.append({"nome": obj["name"], **retangulo})
else:
portas.append(retangulo)
def lista(itens):
return "[\n" + "".join(f" {json.dumps(i, ensure_ascii=False)},\n" for i in itens) + "]"
retangulo_ts = "x: number; z: number; largura: number; altura: number"
ts = f"""// Gerado por `bash 3d/mapa.sh` (`python3 3d/mapa.py converter`) a partir de 3d/mapa/escritorio.tmx: não edite à mão.
/** Planta do escritório para `lerMapa`: `#` parede ou móvel alto, `M` móvel baixo, `.` chão, `J` início, `N` nascedouro. */
export const ESCRITORIO: readonly string[] = {lista("".join(linha) for linha in linhas)};
/** Salas com nome, em células (x para a direita, z para baixo). */
export const AMBIENTES: readonly {{ nome: string; {retangulo_ts} }}[] = {lista(ambientes)};
/** Células abertas nas paredes, em células. */
export const PORTAS: readonly {{ {retangulo_ts} }}[] = {lista(portas)};
"""
SAIDA_TS.parent.mkdir(parents=True, exist_ok=True)
SAIDA_TS.write_text(ts, encoding="utf-8")
if __name__ == "__main__":
comandos = {"gerar": gerar, "converter": converter}
if len(sys.argv) != 2 or sys.argv[1] not in comandos:
sys.exit("uso: python3 3d/mapa.py gerar|converter")
comandos[sys.argv[1]]()
Executable
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Refaz o mapa do escritório a partir de 3d/mapa/escritorio.tmx, a fonte editada no Tiled: exporta JSON e PNG pelo
# Tiled (sem janela), converte para a grade da lógica e monta o 3D no Blender. Rodar de qualquer lugar:
# bash 3d/mapa.sh só exporta, converte e monta
# bash 3d/mapa.sh --gerar antes, refaz o .tmx pelo 3d/mapa.py (apaga o que foi editado à mão no Tiled)
set -euo pipefail
cd "$(dirname "$0")/.."
tiled() {
env -u DISPLAY -u WAYLAND_DISPLAY flatpak run --env=QT_QPA_PLATFORM=offscreen --command="$1" org.mapeditor.Tiled "${@:2}"
}
case "${1:-}" in
"") ;;
--gerar)
echo "aviso: --gerar sobrescreve 3d/mapa/escritorio.tmx pelo 3d/mapa.py; o que foi editado no Tiled se perde" >&2
python3 3d/mapa.py gerar
;;
*)
echo "uso: bash 3d/mapa.sh [--gerar]" >&2
exit 2
;;
esac
tiled tiled --export-map json "$PWD/3d/mapa/escritorio.tmx" "$PWD/3d/mapa/escritorio.json"
tiled tmxrasterizer "$PWD/3d/mapa/escritorio.tmx" "$PWD/3d/mapa/escritorio.png"
python3 3d/mapa.py converter
./node_modules/.bin/biome format --write lib/jogo/mapas/escritorio.ts 3d/mapa/escritorio.json
blender -b -P 3d/mapa_3d.py
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

+477
View File
@@ -0,0 +1,477 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.12.2" orientation="orthogonal" renderorder="right-down" width="44" height="32" tilewidth="32" tileheight="32" infinite="0" nextlayerid="4" nextobjectid="105">
<properties>
<property name="notas">Escritório do jogo em /jogo, 1 célula = 1 m. Este arquivo é a fonte do mapa: abra no Tiled (`flatpak run org.mapeditor.Tiled`), edite, salve e rode `bash 3d/mapa.sh`, que exporta o JSON, converte a grade da lógica e monta o 3D. `bash 3d/mapa.sh --gerar` refaz este arquivo pelo 3d/mapa.py e apaga as edições feitas aqui.
Camadas: planta (parede e chão), moveis (a propriedade grade de cada ladrilho diz como entra na lógica: # bloqueia passo e visão, M bloqueia o passo e deixa o tiro passar, vazio é só enfeite), marcas (objetos pelo tipo).
Tipos em marcas: inicio (ponto do jogador), nascedouro (ponto; o nome diz o lugar), ambiente (retângulo com o nome da sala), porta (retângulo sobre as células abertas na parede), luminaria (ponto no teto), cartaz (ponto na face da parede; face = para onde o cartaz olha: N para cima no desenho, S para baixo, L para a direita, O para a esquerda; imagem de 1 a 4).</property>
<property name="semente" type="int" value="20261110"/>
</properties>
<tileset firstgid="1" name="ladrilhos" tilewidth="32" tileheight="32" tilecount="10" columns="10">
<image source="ladrilhos.png" width="320" height="32"/>
<tile id="0">
<properties>
<property name="nome" value="parede"/>
<property name="grade" value="#"/>
</properties>
</tile>
<tile id="1">
<properties>
<property name="nome" value="chão"/>
<property name="grade" value="."/>
</properties>
</tile>
<tile id="2">
<properties>
<property name="nome" value="mesa"/>
<property name="grade" value="M"/>
</properties>
</tile>
<tile id="3">
<properties>
<property name="nome" value="cadeira"/>
<property name="grade" value=""/>
</properties>
</tile>
<tile id="4">
<properties>
<property name="nome" value="armário"/>
<property name="grade" value="#"/>
</properties>
</tile>
<tile id="5">
<properties>
<property name="nome" value="estante de servidor"/>
<property name="grade" value="#"/>
</properties>
</tile>
<tile id="6">
<properties>
<property name="nome" value="sofá"/>
<property name="grade" value="M"/>
</properties>
</tile>
<tile id="7">
<properties>
<property name="nome" value="bancada"/>
<property name="grade" value="M"/>
</properties>
</tile>
<tile id="8">
<properties>
<property name="nome" value="planta de vaso"/>
<property name="grade" value=""/>
</properties>
</tile>
<tile id="9">
<properties>
<property name="nome" value="bebedouro"/>
<property name="grade" value=""/>
</properties>
</tile>
</tileset>
<layer id="1" name="planta" width="44" height="32">
<data encoding="csv">
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
1,1,1,1,2,2,1,1,1,1,1,1,2,2,1,1,1,1,2,2,1,1,1,1,2,2,1,1,1,1,2,2,1,1,1,1,1,1,1,1,1,1,1,1,
1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,1,
1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,1,
1,2,2,2,2,2,2,2,2,1,2,2,1,1,1,1,1,1,1,1,1,2,2,1,1,1,1,1,2,2,1,1,2,2,1,2,2,2,2,2,2,2,2,1,
1,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,1,
1,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,1,
1,2,2,2,2,2,2,2,2,1,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,1,2,2,2,2,2,2,2,2,1,
1,2,2,2,2,2,2,2,2,1,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,1,2,2,2,2,2,2,2,2,1,
1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,1,
1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2,2,1,1,1,1,
1,1,1,2,2,2,2,2,2,1,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,1,2,2,2,2,2,2,2,2,1,
1,1,1,2,2,2,2,2,2,1,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,1,2,2,2,2,2,2,2,2,1,
1,1,1,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,1,
1,1,1,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,1,
1,1,1,2,2,2,2,2,2,1,2,2,1,1,1,1,1,1,1,1,1,2,2,1,1,1,1,1,1,1,1,1,2,2,1,2,2,2,2,2,2,2,2,1,
1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,1,
1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,1,
1,1,1,1,2,2,1,1,1,1,1,1,1,2,2,1,1,1,1,1,2,2,1,1,1,1,1,1,2,2,1,1,1,1,1,1,1,1,2,2,1,1,1,1,
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
1,1,1,1,2,2,1,1,1,2,2,2,2,2,2,2,2,1,2,2,2,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
1,1,1,1,2,2,1,1,1,2,2,2,2,2,2,2,2,1,2,2,2,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
</data>
</layer>
<layer id="2" name="moveis" width="44" height="32">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,9,0,7,7,7,0,0,0,0,0,0,0,0,10,0,9,0,0,0,0,0,0,0,0,0,0,9,0,7,7,7,0,0,0,0,0,0,0,0,0,0,5,0,
0,0,0,0,0,0,0,0,0,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,5,0,
0,7,0,0,0,0,0,0,8,8,8,8,0,0,0,0,0,4,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,3,3,3,3,0,0,0,5,0,
0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,4,0,0,0,0,0,0,0,0,3,3,3,3,0,0,0,5,0,
0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,4,0,0,0,0,0,
0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,10,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,6,6,6,6,6,0,
0,8,0,4,3,3,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,8,0,0,3,3,4,0,0,0,0,0,0,0,0,4,0,4,4,4,0,0,0,4,0,4,0,4,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,8,0,0,0,4,0,0,0,0,0,0,0,0,0,3,3,3,3,3,0,0,0,3,3,3,3,3,0,0,5,0,0,0,0,0,0,6,6,6,6,6,6,0,
0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,0,0,0,3,3,3,3,3,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,8,8,8,8,0,0,5,5,0,0,0,0,0,0,4,4,4,4,4,0,0,0,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,4,4,0,4,0,0,0,0,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,8,8,8,8,0,0,0,0,0,0,3,3,3,3,3,0,0,0,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,0,0,0,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,4,4,0,0,0,4,0,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,5,5,5,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,9,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,0,5,5,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,8,8,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,8,8,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,7,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="3" name="marcas">
<object id="1" name="inicio" type="inicio" x="240" y="176">
<point/>
</object>
<object id="2" name="elevador 1" type="nascedouro" x="80" y="880">
<point/>
</object>
<object id="3" name="elevador 2" type="nascedouro" x="240" y="880">
<point/>
</object>
<object id="4" name="escada" type="nascedouro" x="624" y="944">
<point/>
</object>
<object id="5" name="banheiros" type="nascedouro" x="208" y="688">
<point/>
</object>
<object id="6" name="fundos, área de serviço" type="nascedouro" x="848" y="944">
<point/>
</object>
<object id="7" name="fundos, área de serviço" type="nascedouro" x="1328" y="976">
<point/>
</object>
<object id="8" name="fundos, almoxarifado" type="nascedouro" x="1328" y="720">
<point/>
</object>
<object id="9" name="recepção" type="ambiente" x="32" y="32" width="448" height="192">
</object>
<object id="10" name="sala de reunião" type="ambiente" x="512" y="32" width="384" height="192">
</object>
<object id="11" name="diretoria" type="ambiente" x="928" y="32" width="448" height="192">
</object>
<object id="12" name="copa" type="ambiente" x="32" y="256" width="256" height="256">
</object>
<object id="13" name="banheiros" type="ambiente" x="32" y="544" width="256" height="224">
</object>
<object id="14" name="área de mesas" type="ambiente" x="416" y="352" width="576" height="320">
</object>
<object id="15" name="sala do servidor" type="ambiente" x="1120" y="256" width="256" height="256">
</object>
<object id="16" name="almoxarifado" type="ambiente" x="1120" y="544" width="256" height="224">
</object>
<object id="17" name="hall dos elevadores" type="ambiente" x="32" y="800" width="512" height="192">
</object>
<object id="18" name="escada" type="ambiente" x="576" y="800" width="192" height="192">
</object>
<object id="19" name="área de serviço" type="ambiente" x="800" y="800" width="576" height="192">
</object>
<object id="20" name="recepção – copa" type="porta" x="128" y="224" width="64" height="32">
</object>
<object id="21" name="recepção – corredor" type="porta" x="384" y="224" width="64" height="32">
</object>
<object id="22" name="sala de reunião – corredor" type="porta" x="576" y="224" width="64" height="32">
</object>
<object id="23" name="sala de reunião – corredor" type="porta" x="768" y="224" width="64" height="32">
</object>
<object id="24" name="sala de reunião – diretoria" type="porta" x="896" y="96" width="32" height="64">
</object>
<object id="25" name="diretoria – corredor" type="porta" x="960" y="224" width="64" height="32">
</object>
<object id="26" name="copa – corredor" type="porta" x="288" y="352" width="32" height="64">
</object>
<object id="27" name="banheiros – corredor" type="porta" x="288" y="608" width="32" height="64">
</object>
<object id="28" name="banheiros – hall dos elevadores" type="porta" x="128" y="768" width="64" height="32">
</object>
<object id="29" name="hall dos elevadores – corredor" type="porta" x="416" y="768" width="64" height="32">
</object>
<object id="30" name="hall dos elevadores – escada" type="porta" x="544" y="864" width="32" height="64">
</object>
<object id="31" name="escada – corredor" type="porta" x="640" y="768" width="64" height="32">
</object>
<object id="32" name="área de serviço – corredor" type="porta" x="896" y="768" width="64" height="32">
</object>
<object id="33" name="área de serviço – almoxarifado" type="porta" x="1216" y="768" width="64" height="32">
</object>
<object id="34" name="almoxarifado – corredor" type="porta" x="1088" y="608" width="32" height="64">
</object>
<object id="35" name="sala do servidor – almoxarifado" type="porta" x="1216" y="512" width="64" height="32">
</object>
<object id="36" name="sala do servidor – corredor" type="porta" x="1088" y="352" width="32" height="64">
</object>
<object id="37" name="área de mesas – corredor norte" type="porta" x="672" y="320" width="64" height="32">
</object>
<object id="38" name="área de mesas – corredor norte" type="porta" x="896" y="320" width="64" height="32">
</object>
<object id="39" name="área de mesas – corredor sul" type="porta" x="672" y="672" width="64" height="32">
</object>
<object id="40" name="área de mesas – corredor oeste" type="porta" x="384" y="480" width="32" height="64">
</object>
<object id="41" name="área de mesas – corredor leste" type="porta" x="992" y="480" width="32" height="64">
</object>
<object id="42" name="luminaria" type="luminaria" x="112" y="128">
<point/>
</object>
<object id="43" name="luminaria" type="luminaria" x="256" y="128">
<point/>
</object>
<object id="44" name="luminaria" type="luminaria" x="400" y="128">
<point/>
</object>
<object id="45" name="luminaria" type="luminaria" x="608" y="128">
<point/>
</object>
<object id="46" name="luminaria" type="luminaria" x="800" y="128">
<point/>
</object>
<object id="47" name="luminaria" type="luminaria" x="1008" y="128">
<point/>
</object>
<object id="48" name="luminaria" type="luminaria" x="1152" y="128">
<point/>
</object>
<object id="49" name="luminaria" type="luminaria" x="1296" y="128">
<point/>
</object>
<object id="50" name="luminaria" type="luminaria" x="96" y="320">
<point/>
</object>
<object id="51" name="luminaria" type="luminaria" x="224" y="320">
<point/>
</object>
<object id="52" name="luminaria" type="luminaria" x="96" y="448">
<point/>
</object>
<object id="53" name="luminaria" type="luminaria" x="224" y="448">
<point/>
</object>
<object id="54" name="luminaria" type="luminaria" x="96" y="656">
<point/>
</object>
<object id="55" name="luminaria" type="luminaria" x="224" y="656">
<point/>
</object>
<object id="56" name="luminaria" type="luminaria" x="480" y="432">
<point/>
</object>
<object id="57" name="luminaria" type="luminaria" x="640" y="432">
<point/>
</object>
<object id="58" name="luminaria" type="luminaria" x="768" y="432">
<point/>
</object>
<object id="59" name="luminaria" type="luminaria" x="928" y="432">
<point/>
</object>
<object id="60" name="luminaria" type="luminaria" x="480" y="592">
<point/>
</object>
<object id="61" name="luminaria" type="luminaria" x="640" y="592">
<point/>
</object>
<object id="62" name="luminaria" type="luminaria" x="768" y="592">
<point/>
</object>
<object id="63" name="luminaria" type="luminaria" x="928" y="592">
<point/>
</object>
<object id="64" name="luminaria" type="luminaria" x="1184" y="320">
<point/>
</object>
<object id="65" name="luminaria" type="luminaria" x="1312" y="320">
<point/>
</object>
<object id="66" name="luminaria" type="luminaria" x="1184" y="448">
<point/>
</object>
<object id="67" name="luminaria" type="luminaria" x="1312" y="448">
<point/>
</object>
<object id="68" name="luminaria" type="luminaria" x="1184" y="656">
<point/>
</object>
<object id="69" name="luminaria" type="luminaria" x="1312" y="656">
<point/>
</object>
<object id="70" name="luminaria" type="luminaria" x="112" y="896">
<point/>
</object>
<object id="71" name="luminaria" type="luminaria" x="288" y="896">
<point/>
</object>
<object id="72" name="luminaria" type="luminaria" x="464" y="896">
<point/>
</object>
<object id="73" name="luminaria" type="luminaria" x="672" y="896">
<point/>
</object>
<object id="74" name="luminaria" type="luminaria" x="864" y="896">
<point/>
</object>
<object id="75" name="luminaria" type="luminaria" x="1024" y="896">
<point/>
</object>
<object id="76" name="luminaria" type="luminaria" x="1152" y="896">
<point/>
</object>
<object id="77" name="luminaria" type="luminaria" x="1312" y="896">
<point/>
</object>
<object id="78" name="luminaria" type="luminaria" x="400" y="288">
<point/>
</object>
<object id="79" name="luminaria" type="luminaria" x="544" y="288">
<point/>
</object>
<object id="80" name="luminaria" type="luminaria" x="704" y="288">
<point/>
</object>
<object id="81" name="luminaria" type="luminaria" x="864" y="288">
<point/>
</object>
<object id="82" name="luminaria" type="luminaria" x="1008" y="288">
<point/>
</object>
<object id="83" name="luminaria" type="luminaria" x="400" y="736">
<point/>
</object>
<object id="84" name="luminaria" type="luminaria" x="544" y="736">
<point/>
</object>
<object id="85" name="luminaria" type="luminaria" x="704" y="736">
<point/>
</object>
<object id="86" name="luminaria" type="luminaria" x="864" y="736">
<point/>
</object>
<object id="87" name="luminaria" type="luminaria" x="1008" y="736">
<point/>
</object>
<object id="88" name="luminaria" type="luminaria" x="352" y="416">
<point/>
</object>
<object id="89" name="luminaria" type="luminaria" x="352" y="608">
<point/>
</object>
<object id="90" name="luminaria" type="luminaria" x="1056" y="416">
<point/>
</object>
<object id="91" name="luminaria" type="luminaria" x="1056" y="608">
<point/>
</object>
<object id="92" name="cartaz" type="cartaz" x="240" y="32">
<properties>
<property name="face" value="S"/>
<property name="imagem" type="int" value="1"/>
</properties>
<point/>
</object>
<object id="93" name="cartaz" type="cartaz" x="400" y="32">
<properties>
<property name="face" value="S"/>
<property name="imagem" type="int" value="1"/>
</properties>
<point/>
</object>
<object id="94" name="cartaz" type="cartaz" x="688" y="32">
<properties>
<property name="face" value="S"/>
<property name="imagem" type="int" value="3"/>
</properties>
<point/>
</object>
<object id="95" name="cartaz" type="cartaz" x="512" y="144">
<properties>
<property name="face" value="L"/>
<property name="imagem" type="int" value="3"/>
</properties>
<point/>
</object>
<object id="96" name="cartaz" type="cartaz" x="1168" y="32">
<properties>
<property name="face" value="S"/>
<property name="imagem" type="int" value="2"/>
</properties>
<point/>
</object>
<object id="97" name="cartaz" type="cartaz" x="288" y="464">
<properties>
<property name="face" value="O"/>
<property name="imagem" type="int" value="2"/>
</properties>
<point/>
</object>
<object id="98" name="cartaz" type="cartaz" x="656" y="320">
<properties>
<property name="face" value="N"/>
<property name="imagem" type="int" value="4"/>
</properties>
<point/>
</object>
<object id="99" name="cartaz" type="cartaz" x="320" y="592">
<properties>
<property name="face" value="L"/>
<property name="imagem" type="int" value="3"/>
</properties>
<point/>
</object>
<object id="100" name="cartaz" type="cartaz" x="1088" y="464">
<properties>
<property name="face" value="O"/>
<property name="imagem" type="int" value="2"/>
</properties>
<point/>
</object>
<object id="101" name="cartaz" type="cartaz" x="816" y="704">
<properties>
<property name="face" value="S"/>
<property name="imagem" type="int" value="1"/>
</properties>
<point/>
</object>
<object id="102" name="cartaz" type="cartaz" x="656" y="352">
<properties>
<property name="face" value="S"/>
<property name="imagem" type="int" value="2"/>
</properties>
<point/>
</object>
<object id="103" name="cartaz" type="cartaz" x="416" y="592">
<properties>
<property name="face" value="L"/>
<property name="imagem" type="int" value="1"/>
</properties>
<point/>
</object>
<object id="104" name="cartaz" type="cartaz" x="368" y="800">
<properties>
<property name="face" value="S"/>
<property name="imagem" type="int" value="3"/>
</properties>
<point/>
</object>
</objectgroup>
</map>
Binary file not shown.

After

Width:  |  Height:  |  Size: 468 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 KiB