Compare commits
48 Commits
ecccfddf7d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 87dd8e1190 | |||
| 3bffb82560 | |||
| f181bf4de9 | |||
| 7dd6bfc40c | |||
| 0d080a8d94 | |||
| 613af7d7ea | |||
| b10641ee3c | |||
| 5b10d63970 | |||
| fcdd8306d2 | |||
| 01fb83d05f | |||
| 34024bab96 | |||
| 40df66e7cc | |||
| 61c628876f | |||
| 9aebddde86 | |||
| 8ae566d627 | |||
| 6c4f2e640a | |||
| 949a076ec7 | |||
| 14961d903b | |||
| ccac320e0e | |||
| ff3e66ae3e | |||
| 7c92f046a1 | |||
| 9de6a0b5a9 | |||
| 4603a64e10 | |||
| 36a172d07f | |||
| cb85c94f56 | |||
| 3ba7ef92ad | |||
| c326c363a0 | |||
| 7543fe3cb7 | |||
| 93480150e5 | |||
| 201d3be322 | |||
| b6aca99d0c | |||
| 44f58bc9c2 | |||
| 03c97ae3f2 | |||
| 836132b6ef | |||
| 3a6ca792aa | |||
| a8bfaaee36 | |||
| 902da64588 | |||
| e27eedb654 | |||
| a07e2e567f | |||
| 9cf23f8ad6 | |||
| 04623ea226 | |||
| 983104a173 | |||
| 4f9a792dea | |||
| a585dff09d | |||
| d584e418f5 | |||
| a41d39cc78 | |||
| 3ac613cb58 | |||
| e20ca4f016 |
@@ -10,13 +10,7 @@ target/
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
/assets/maps/autosave
|
||||
|
||||
# Added by cargo
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
@@ -0,0 +1,279 @@
|
||||
// Cinema 24/7 -- Navigation
|
||||
// Lobby -> Foyer -> Labyrinth-Etagen -> Eingang Theater 4
|
||||
//
|
||||
// Variablen:
|
||||
// past_it -- Spieler ist an Theater 4 vorbeigegangen (bool)
|
||||
// snaks -- Spieler hat Snacks mitgenommen (0/1)
|
||||
// had_sweets -- Spieler hat süße Snacks gewählt (0/1)
|
||||
//
|
||||
// Übergang zu cinema_f4r4.ink via # start_ink Tag bei Eintreten in Theater 4.
|
||||
// KV-Store überträgt snaks/had_sweets/past_it automatisch ans nächste Script.
|
||||
|
||||
VAR past_it = false
|
||||
VAR snaks = 0
|
||||
VAR had_sweets = 0
|
||||
|
||||
-> the_cinema
|
||||
|
||||
|
||||
== the_cinema ==
|
||||
|
||||
The lobby has a very retro, synth-wave, neon, 70s look to it. The carpet alone makes your eyes hurt. This is not a cheeky metaphor for its ugliness. Your eyes really hurt when you stare at it for any length of time because the shades of blue and red, mixed together on it in geometric patterns, are so bright they make your vision dance in the low light. At a glance, something in the part of your brain that is responsible for processing visual data seems to short-circuit and you see the patterns move against each other, impossibly on the static floor.
|
||||
|
||||
* [>>>]
|
||||
-
|
||||
|
||||
Better look to the ceiling with its wavy cutouts backlit by colorful LED.
|
||||
|
||||
The ticket booth is unmanned of course, as is the snack dispensary. Which seems well stocked at least. The popcorn even looks fresh. Well, it smells fresh at least. The scent of pleasantly burned sugar is heavy in the air.
|
||||
|
||||
The ribbon barrier is closed but easy enough to bypass. Is it closed? It looks open. Though abandoned. But not for long. Not the abandonment of disrepair, only the momentary abandonment place like this experience between beginning and end of the shows. When all the visitors sit in their seats and the staff takes this moment to slip away for a quick smoke break. Something like that. Full behind doors but empty in the open.
|
||||
|
||||
* [>>>]
|
||||
-
|
||||
|
||||
You can even hear sounds that must be a film playing. Screams and gunfire. You hope someone left the door to that particular theatre open and that is why you can hear it so clearly. Because if the sound insulation is that shit, the movie going experience can't be very good here.
|
||||
|
||||
* [Slip under the ticket barrier]
|
||||
You raise the ribbon of the barrier and duck under it. Almost without a thought.
|
||||
-> the_foliar
|
||||
* [Go to the ticket booth]
|
||||
The graspable of the ticket booth is plastered with advertisements, movie posters and prohibition signs informing visitors of behaviours and attire within the cinema. The slip you took for a "closed" sign that hangs in the middle of the glass says the following.
|
||||
|
||||
"No tickets required. Proceed with caution."
|
||||
|
||||
On the little table where someone would place their tickets, there is another slip reading:
|
||||
|
||||
"Warning:
|
||||
|
||||
Everyone is free to enter.
|
||||
|
||||
But it is advised only to visit the auditoriums where no film is currently being played.
|
||||
|
||||
As the films are a bit extreme for most audiences.
|
||||
|
||||
Thank you for your visit!"
|
||||
** [Go into the cinema]
|
||||
You can open the flimsy barrier from here. It is secured right next to where you stand. So you enter the foliar.
|
||||
-> the_foliar
|
||||
** [Leave the cinema] You leave.
|
||||
-> END
|
||||
|
||||
-> the_foliar
|
||||
|
||||
|
||||
== the_foliar ==
|
||||
|
||||
The foliar is big. With couches to wait and meet and lounge on. The room is only broken up by advertising columns. Some of them display movie posters, though most are empty.
|
||||
|
||||
A long bar stretches the length of the back wall. Behind it chips and candy are stacked on shelves like those of an actual bar. It even has a mirrored backwall, which visually multiplies the colourful packaging. This pattern is only broken by enormous popcorn machines, which are full to the brim.
|
||||
|
||||
Do you want to grab a snack?
|
||||
* [Of course]
|
||||
~ snaks = 1
|
||||
** [Sweet] You grab a sweet snack. You are lucky. They have your favourite.
|
||||
~ had_sweets = 1
|
||||
** [Salty] You grab a salty snack. You are lucky. They have your favourite.
|
||||
* [I don't think so] You better not risk it.
|
||||
-
|
||||
To both sides of the room are stairs. This is a multi-floor cinema.
|
||||
-> groundfloor
|
||||
|
||||
|
||||
== groundfloor ==
|
||||
|
||||
+ [Go to the right staircase]
|
||||
{not (f1 > 0 or f2 > 0):
|
||||
You choose the right staircase to ascend to the first floor.
|
||||
|
||||
Pictures of actors in film scenes decorate the walls. They are probably supposed to be famous. Iconically recognisable. You know none of them. However, some catch your eye as familiar until you take a second look. They are certainly not cheap generic-property knockoffs to avoid copyright infringement. It's just a coincidence.
|
||||
|
||||
A big number: 1 marks the landing to the next floor.
|
||||
}
|
||||
{f1 > 0 or f2 > 0:
|
||||
You enter the first floor.
|
||||
}
|
||||
-> f1.r
|
||||
+ [Go to the left staircase]
|
||||
{not (f1 > 0 or f2 > 0):
|
||||
You choose the left staircase to ascend.
|
||||
|
||||
Pictures of actors in film scenes decorate the walls. They are probably supposed to be famous. Iconically recognisable. You know none of them. However, some catch your eye as familiar until you take a second look. They are certainly not cheap generic-property knockoffs to avoid copyright infringement. It's just a coincidence.
|
||||
|
||||
A big number: 2 marks the landing to the next floor.
|
||||
}
|
||||
{f1 > 0 or f2 > 0:
|
||||
You enter the second floor.
|
||||
}
|
||||
-> f2.l
|
||||
|
||||
|
||||
// ── Etagen-Labyrinth ────────────────────────────────────────────────────────
|
||||
// Die Stockwerk-Verbindungen sind absichtlich nicht-euklidisch.
|
||||
// Treppenauf/-ab führen nicht zu linear benachbarten Etagen.
|
||||
|
||||
== f1 ==
|
||||
= r
|
||||
+ [Go down the corridor.] -> f1.l
|
||||
+ [Take the stairs up.]
|
||||
You enter the fifth floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f5.r
|
||||
+ [Take the stairs down.]
|
||||
You enter the sixth floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f6.r
|
||||
|
||||
= l
|
||||
+ [Go down the corridor.] -> f1.r
|
||||
+ [Take the stairs up.]
|
||||
You enter the third floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f3.l
|
||||
+ [Take the stairs down.]
|
||||
You enter the seventh floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f7.l
|
||||
|
||||
|
||||
== f2 ==
|
||||
= r
|
||||
+ [Go down the corridor.] -> f2.l
|
||||
+ [Take the stairs up.]
|
||||
You enter the sixth floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f6.r
|
||||
+ [Take the stairs down.]
|
||||
You enter the seventh floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f7.r
|
||||
|
||||
= l
|
||||
+ [Go down the corridor.] -> f2.r
|
||||
+ [Take the stairs up.]
|
||||
You enter the fifth floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f5.l
|
||||
+ [Take the stairs down.]
|
||||
You enter the first floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f1.l
|
||||
|
||||
|
||||
== f3 ==
|
||||
= r
|
||||
+ [Go down the corridor.] -> f3.l
|
||||
+ [Take the stairs up.]
|
||||
You are back on the ground floor.
|
||||
-> groundfloor
|
||||
+ [Take the stairs down.]
|
||||
You enter the second floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f2.r
|
||||
|
||||
= l
|
||||
+ [Go down the corridor.] -> f3.r
|
||||
+ [Take the stairs up.]
|
||||
You enter the sixth floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f6.l
|
||||
+ [Take the stairs down.]
|
||||
You enter the seventh floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f7.l
|
||||
|
||||
|
||||
== f4 ==
|
||||
= r
|
||||
+ [Go down the corridor.]
|
||||
{not past_it:
|
||||
You go down the corridor. It is empty. Some of the doors are closed. Some are slightly open. When you get close to the door of theatre 4 you can hear soft sounds. There is no film playing in the theatre according to the sign beside the door.
|
||||
- else:
|
||||
The corridor is still the same. And there are still the same faint noises coming out of theatre 4.
|
||||
}
|
||||
** [Enter]
|
||||
So you enter. # start_ink cinema_f4r4.ink.json
|
||||
-> END
|
||||
** [Go past it.]
|
||||
~ past_it = true
|
||||
You go past it.
|
||||
You walk closer to one wall. You want to run your fingers along it while you walk, but can't quite bring yourself to do so. To move the final millimetre closer to the wall. As if you are on tracks. Soon you are at the other side of the floor.
|
||||
-> f4.l
|
||||
+ [Take the stairs up.]
|
||||
You enter the fifth floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f5.l
|
||||
+ [Take the stairs down.]
|
||||
You are back on the ground floor.
|
||||
-> groundfloor
|
||||
|
||||
= l
|
||||
+ [Go down the corridor.]
|
||||
{not past_it:
|
||||
You go down the corridor. It is empty. Some of the doors are closed. Some are slightly open. When you get close to the door of theatre 4 you can hear soft sounds. There is no film playing in the theatre according to the sign beside the door.
|
||||
- else:
|
||||
The corridor is still the same. And there are still the same faint noises coming out of theatre 4.
|
||||
}
|
||||
** [Enter]
|
||||
So you enter. # start_ink cinema_f4r4.ink.json
|
||||
-> END
|
||||
** [Go past it.]
|
||||
~ past_it = true
|
||||
You go past it.
|
||||
You walk closer to one wall. You want to run your fingers along it while you walk, but can't quite bring yourself to do so. To move the final millimetre closer to the wall. As if you are on tracks. Soon you are at the other side of the floor.
|
||||
-> f4.r
|
||||
+ [Take the stairs up.]
|
||||
You enter the seventh floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f7.l
|
||||
+ [Take the stairs down.]
|
||||
You are back on the ground floor.
|
||||
-> groundfloor
|
||||
|
||||
|
||||
== f5 ==
|
||||
= r
|
||||
+ [Go down the corridor.] -> f5.l
|
||||
+ [Take the stairs up.]
|
||||
You enter the second floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f2.r
|
||||
+ [Take the stairs down.]
|
||||
You enter the third floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f3.l
|
||||
|
||||
= l
|
||||
+ [Go down the corridor.] -> f5.r
|
||||
+ [Take the stairs up.]
|
||||
You enter the first floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f1.l
|
||||
+ [Take the stairs down.]
|
||||
You enter the sixth floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f6.l
|
||||
|
||||
|
||||
== f6 ==
|
||||
= r
|
||||
+ [Go down the corridor.] -> f6.l
|
||||
+ [Take the stairs up.]
|
||||
You enter the first floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f1.r
|
||||
+ [Take the stairs down.]
|
||||
You enter the fourth floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.}}
|
||||
-> f4.r
|
||||
|
||||
= l
|
||||
+ [Go down the corridor.] -> f6.r
|
||||
+ [Take the stairs up.]
|
||||
You are back on the ground floor.
|
||||
-> groundfloor
|
||||
+ [Take the stairs down.]
|
||||
You enter the third floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f3.l
|
||||
|
||||
|
||||
== f7 ==
|
||||
= r
|
||||
+ [Go down the corridor.] -> f7.l
|
||||
+ [Take the stairs up.]
|
||||
You enter the third floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f3.r
|
||||
+ [Take the stairs down.]
|
||||
You enter the fifth floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f5.r
|
||||
|
||||
= l
|
||||
+ [Go down the corridor.] -> f7.r
|
||||
+ [Take the stairs up.]
|
||||
You enter the fourth floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.}}
|
||||
-> f4.l
|
||||
+ [Take the stairs down.]
|
||||
You enter the second floor. {~It looks like all the others.|||{~It looks the same as all the others.|More hallway. More carpet.|Just another floor}}
|
||||
-> f2.l
|
||||
|
||||
@@ -0,0 +1,856 @@
|
||||
// Cinema 24/7 -- Theater 4: Nails
|
||||
// Begegnung mit Nails, der Schattenkünstlerin.
|
||||
//
|
||||
// Variablen:
|
||||
// favor_nails -- Nails' Stimmung; > 0 = Sympathie, < 0 = Genervtheit
|
||||
// past_it -- aus cinema.ink via KV: Spieler ist an Theater 4 vorbeigegangen
|
||||
// snaks -- aus cinema.ink via KV: Spieler hat Snacks (0/1)
|
||||
// had_sweets -- aus cinema.ink via KV: Spieler hat Süßes gewählt (0/1)
|
||||
// fallbear -- temporär: Bär verliert Gleichgewicht beim Ausweichen
|
||||
// boop_success -- temporär: Einhorn-Nase boopen erfolgreich (0/1)
|
||||
//
|
||||
// Stub-Knoten (noch zu schreiben):
|
||||
// the_show, cbear, shadow_battle, apprentice, quitter,
|
||||
// her_portfolio, performance, practice, seeulater
|
||||
|
||||
VAR favor_nails = 0
|
||||
VAR past_it = false
|
||||
VAR snaks = 0
|
||||
VAR had_sweets = 0
|
||||
VAR fallbear = false
|
||||
VAR boop_success = false
|
||||
|
||||
-> f4r4
|
||||
|
||||
|
||||
== f4r4 ==
|
||||
|
||||
You push the door open to a few steps bringing you the level where the seats are. The theatre screen is lit in a faint pink that floods the dingy room with a pleasant atmosphere. The seats lie in shadow, but they look empty.
|
||||
|
||||
+ [Go to the screen] -> f4r4.to_the_screen
|
||||
+ [Sit down in the closest seat] -> f4r4.frontrow_to_show
|
||||
+ [Go to the back] -> f4r4.backseat
|
||||
|
||||
= to_the_screen
|
||||
You go through the aisle to the very front. You are so close to the screen here that your entire field of view is absorbed by this shade of pink, if you angle your head right.
|
||||
|
||||
+ [Go closer to the screen so your shadow falls on it]
|
||||
You take a few more steps until your silhouette emerges as a dark shape from the dark lower border of the screen.
|
||||
++ [Raise your arms] You raise your arms to see your shadow do the same.
|
||||
++ [Walk to the side] You walk a few steps to the side to see your shadow do the same.
|
||||
--
|
||||
Until the enormous shape of a wolf jumps at you from the side. You only see it come for a second before you flinch away.
|
||||
|
||||
But there is no impact, it is just another shadow. It looked very real out of the corner of your eye. On a second look, you see that it is the projection of masterfully interwoven fingers, cast from the back of the theatre.
|
||||
-> f4r4.first_duet
|
||||
+ [Turn back to the seats]
|
||||
But you turn away from it and to the seats again. From here you have a better view of the theatre and can see a shape, sitting in the elevated rows on the back half of the room.
|
||||
-
|
||||
A voice comes from there.
|
||||
|
||||
Her: OMG! I totally don't believe it? Is that an audience? OK. OK. OK, sit down. The show is about to start.
|
||||
++ [Take a seat]
|
||||
You sit down in the front row and look up to the screen.
|
||||
-> f4r4.the_show
|
||||
++ [Go to the person]
|
||||
You are more curious about this person than the show. So you decide to make your way up the dark rows of seats to get a better look.
|
||||
-> f4r4.the_performer
|
||||
|
||||
= frontrow_to_show
|
||||
You slump down in the closest seat. You are fairly far to the front. So you can lie your head down on the back of the seat to look up into the abyss of pink.
|
||||
|
||||
You have huffed out only one deep breath before the shadow of a giant hand rips the light and colour of the screen apart.
|
||||
|
||||
+ [I'm startled]
|
||||
You startle at that and turn around in your seat to look for the source of the shadow.
|
||||
|
||||
Against the glare of the projector you can just about make out two hands which are raised over the rows of seats.
|
||||
|
||||
You raise a hand against the bright light. But before your eyes can adjust to the dark, you hear a voice.
|
||||
|
||||
Her: Eyes front, please. Or you'll miss the show.
|
||||
|
||||
The voice has the twang of mock annoyance.
|
||||
++ [Turn to the screen] You comply and look to the screen again. -> f4r4.the_show
|
||||
++ [Go to investigate] You get up and go towards the back, where the figure is. -> f4r4.the_performer
|
||||
--
|
||||
+ [This is fine.]
|
||||
++ [I turn to look for the shadow maker]
|
||||
You turn around in your seat to look for the source of the shadow.
|
||||
|
||||
Against the glare of the projector you can just about make out two hands which are raised over the landscape of cushioned backrests.
|
||||
|
||||
You raise a hand against the bright light. But before your eyes can adjust to the dark, you hear a voice.
|
||||
|
||||
Her: Eyes front, please. Or you'll miss the show.
|
||||
|
||||
The voice has the twang of mock annoyance.
|
||||
+++ [Turn to the screen] You comply and look to the screen again. -> f4r4.the_show
|
||||
+++ [Go to investigate] You get up and go towards the back, where the figure is. -> f4r4.the_performer
|
||||
---
|
||||
++ [I keep looking at the screen]
|
||||
You keep your eyes on the screen unperturbed and follow the movements of the shadows with your eyes.
|
||||
-> f4r4.silent_show
|
||||
|
||||
= backseat
|
||||
When you are at the cinema, do you always prefer the seats at the very back? You make your way there now, automatically. You shimmy into the aisles and pick a seat. You sit down. It is comfortable. It is fine. You relax, with the whole room in view.
|
||||
-> f4r4.the_show
|
||||
|
||||
= the_performer
|
||||
Her: No. It's fine. You can sit down in the front as well. Just there will be fine.
|
||||
|
||||
+ [Continue to the back]
|
||||
~ favor_nails = favor_nails - 1
|
||||
You continue your way despite her protests.
|
||||
|
||||
Her: Just sit down! Urgh. Now I have to delay the performance.
|
||||
|
||||
You have reached the row where the figure sits and huffs unhappily at you. You can still only see their outline in the dark. Something about it is strange. Their head is oddly big. It has a weird shape, too.
|
||||
|
||||
Her: Fine, you can sit in the back row if you have to. No respect for the craft.
|
||||
|
||||
+ [Take a seat where you are] She sounds annoyed. You decide to obey and sit down in the middle row. -> f4r4.the_show
|
||||
- (just_sit_damnit)
|
||||
* [Who are you?]
|
||||
You: Who are you?
|
||||
|
||||
Her: Only the greatest visual artist you will ever meet. Not that you would be able to appreciate it, since you refuse to sit down for the performance to start.
|
||||
|
||||
++ [That's not really an answer.]
|
||||
~ favor_nails = favor_nails - 3
|
||||
You: That's not really an answer.
|
||||
|
||||
Her: Oh mahhai, unbelievable. Don't be such a pain in the ass just sit down or get out of my theatre.
|
||||
|
||||
+++ [What kind of performance?]
|
||||
~ favor_nails = favor_nails - 1
|
||||
You: What kind of performance?
|
||||
|
||||
Her: Are you for fudging real? Just sit down and find out?
|
||||
-> just_sit_damnit
|
||||
+++ [Can I sit with you?]
|
||||
Her: I need some space to perform. Just keep a little bit of a distance.
|
||||
++++ [Sit down across the aisle] You just sit down in the first seat across the aisle from her. -> f4r4.the_show
|
||||
++++ [Take a seat two places away from her]
|
||||
You shimmy into the aisle where she is and sit down with a double seat of space to her. A side glance in her direction subjects ruffled knit fabrics and jangling bracelets.
|
||||
-> f4r4.the_show
|
||||
++++ [Sit down in the back row]
|
||||
You'd actually prefer to sit in the back row. So you go and take your seat, your back against the wall, with the whole theatre in view.
|
||||
-> f4r4.the_show
|
||||
+++ [You just sit your ass down across the aisle from her]
|
||||
You just sit down in the first seat across the aisle from her.
|
||||
-> f4r4.the_show
|
||||
+++ [Sit down in the back row]
|
||||
You prefer to sit in the back row. So you go and take your seat, your back against the wall, with the whole theatre in view.
|
||||
-> f4r4.the_show
|
||||
--
|
||||
* [Can I sit with you?]
|
||||
~ favor_nails = favor_nails + 1
|
||||
You: Can I sit with you?
|
||||
|
||||
Her: Oh. Well, actually I need a bit of space to perform. But you can sit here if you want.
|
||||
|
||||
You can just make out a hand with long, pink acrylic nails and glittering rings gesturing to a place two seats away from her.
|
||||
|
||||
You shimmy into the aisle and take the seat. A side glance in her direction subjects ruffled knit fabrics and jangling bracelets.
|
||||
-> f4r4.the_show
|
||||
* [Sit down across the aisle from her]
|
||||
You sit down in the first seat across the aisle from her.
|
||||
-> f4r4.the_show
|
||||
* [Sit down in the back row]
|
||||
You prefer to sit in the back row. So you go and take your seat, your back against the wall, with the whole theatre in view.
|
||||
-> f4r4.the_show
|
||||
|
||||
= first_duet
|
||||
* [Try to see the one making the shadow]
|
||||
You turn, raising your hand against the light of the projector. But too late, the glare has already gotten into your eyes and you can't make out anything in the darkness of the room.
|
||||
|
||||
An amused voice comes out of the darkness.
|
||||
|
||||
Her: Watch out, here it comes again.
|
||||
|
||||
++ [Turn to the screen]
|
||||
Turn back towards the screen and see the wolf pace your shadow. Readying itself for the next attack.
|
||||
-> f4r4.first_duet
|
||||
++ [Go investigate]
|
||||
You start up the aisle between the seats.
|
||||
|
||||
A voice comes out of the darkness.
|
||||
|
||||
Her: Whahat? Don't tell me you didn't like that. That was A-Class theatre. You know I don't get an audience here often. Take a seat and I'll show you a real show.
|
||||
-> f4r4.the_performer
|
||||
* (bunny)[Make the shadow figure of a bunny, to run from the wolf]
|
||||
You position your hands to make the figure of a bunny.
|
||||
++ Its ears wiggle nervously. [] The ears of the wolf twitch in turn, with anticipation.
|
||||
++ It rubs its nose with its front paws in an attempt to be too cute to be eaten. [] But there is only a low hungry growl from the back of the theatre.
|
||||
--
|
||||
Your bunny decides to make a run for it.
|
||||
|
||||
The wolf jumps after it with graceful movements. It is quick and deadly.
|
||||
|
||||
++ [The bunny flees off screen]
|
||||
Your bunny flees to the side of the screen and jumps into the unlit void. Trusting that nonexistence will keep it safe from the jaws of the predator.
|
||||
-> f4r4.quitter
|
||||
++ [The bunny flees across the screen]
|
||||
Your bunny hops across the screen. You have to be fast and clever and change directions often to get away from the monster chasing you.
|
||||
--
|
||||
Soon you are short of breath and turn once more to see the wolf mid-air just above you. Maw wide, ready to consume you. The running is over. You only have a second to decide what to do.
|
||||
|
||||
++ [Accept your fate]
|
||||
So you accept your fate and cower the bunny down in a final primal reflex, before the wolf is on top of you and swallows you whole.
|
||||
|
||||
You are blacked out by the shape of it for a moment and can see the hands raised above the seats silhouetted against the rays of beamer light. Maybe it is just the perspective or something, but they seem gigantic.
|
||||
|
||||
Then the shadow of the wolf moves beside you again. The light punches you in the face and it wags its tail. A voice comes from the back of the room.
|
||||
|
||||
Her: Ahhh! That was SOOOOO FUN. More. More. More.
|
||||
|
||||
+++ [I think I need to catch my breath first.]
|
||||
You: I think I need to catch my breath first.
|
||||
|
||||
Her: Hm. Okie doke. You wanna see one of my specialities?
|
||||
|
||||
++++ [Sure.] You: Sure.
|
||||
You take your front row seat and look up to the screen.
|
||||
-> f4r4.the_show
|
||||
++++ [Who are you?]
|
||||
You: Who are you?
|
||||
|
||||
Her: I'm the baddest bitch alive. Now sit your ass down and let me put on a show.
|
||||
----
|
||||
-> f4r4.the_show
|
||||
++++ [Sit] You indulge her and sit. -> f4r4.the_show
|
||||
++++ [Go up to her] You make your way up the aisles to her. -> f4r4.the_performer
|
||||
+++ [OK. But next time I won't be such easy prey.]
|
||||
You: OK. But next time I won't be such easy prey.
|
||||
|
||||
Her: Uh. You want a shadow battle? Bring it oooooon.
|
||||
-> f4r4.shadow_battle
|
||||
---
|
||||
++ (cheat)[Turn into a crocodile]
|
||||
So you dissolve the bunny to spread your arms wide. Creating the long jaws of a crocodile. It is a desperate move and unexpected enough that the wolf can't stop before plunging dead first into the trap you just created. You snap your arms together.
|
||||
|
||||
The wolf's whine becomes a whiny voice.
|
||||
|
||||
Her: NOOOOOOO. That's not fair. Not fair. Not fair.
|
||||
|
||||
+++ [Says who?]
|
||||
You: Says who?
|
||||
|
||||
Her: Says ME! You can't just change the rules like that.
|
||||
|
||||
You: I wasn't aware there were any rules.
|
||||
|
||||
Her: But I wanted to eat you!
|
||||
|
||||
++++ [You can eat my ass.]
|
||||
She laughs.
|
||||
|
||||
Her: EW! I don't even know you.
|
||||
|
||||
+++++ [We can change that.]
|
||||
~ favor_nails = favor_nails + 1
|
||||
You: We can change that.
|
||||
|
||||
Her: WOW. You coming on strong. I can't even tell if you're cute from up here. We will have to meet somewhere where it is light, some time.
|
||||
|
||||
++++++ [It's a date.] You: It's a date.
|
||||
She giggles.
|
||||
------
|
||||
Her: I wanna practice some more of my figures. Do you wanna watch?
|
||||
-> f4r4.practice
|
||||
++++++ [How about now?] You: How about now?
|
||||
------
|
||||
Her: I wanna practice some more of my figures. Do you wanna watch?
|
||||
-> f4r4.practice
|
||||
+++++ [I don't know you either.]
|
||||
You: I don't know you either.
|
||||
|
||||
Her: You know my work. So you already know I'm fab as fuck.
|
||||
|
||||
++++++ [True.] You: True.
|
||||
Her: Do you wanna see some more?
|
||||
+++++++ [Sure.] You: Sure. -> f4r4.the_show
|
||||
+++++++ [I would actually like to get to know you.]
|
||||
You: I would actually like to get to know you.
|
||||
|
||||
Her: HHHM. I actually wanna practice some more figures. But we can grab snacks after.
|
||||
|
||||
++++++++ [Can I watch you practice?]
|
||||
You: Can I watch you practice?
|
||||
Her: Sure.
|
||||
-> f4r4.practice
|
||||
++++++++ [Should I come back later to pick you up?]
|
||||
You: Should I come back later to pick you up?
|
||||
Her: If you want.
|
||||
--------
|
||||
You: OK, I will see you in a bit.
|
||||
-> f4r4.seeulater
|
||||
-------
|
||||
Her: Nice. What do you want to see? My portfolio or a Full Performance?
|
||||
+++++++ [Portfolio] You: Your portfolio? Now I'm curious. -> f4r4.her_portfolio
|
||||
+++++++ [Performance] You: I would like to see a performance. -> f4r4.performance
|
||||
-----
|
||||
++++ [No don't eat me. I can get you popcorn instead.]
|
||||
Her: NAWWW! OK. Fair is fair.
|
||||
+++ [Sorry.]
|
||||
You: Sorry. But I don't want to die today.
|
||||
|
||||
Her: Well, you shouldn't have been a bunny then, gorgeous. A wolf has to eat.
|
||||
* [Make the shadow figure of a bear, to confront the wolf]
|
||||
You make a bear...
|
||||
|
||||
Do you know how to make a bear shadow figure?
|
||||
|
||||
++ [YES]
|
||||
OK. You make a bear. But due to your position relative to the screen it is too tiny to confront the wolf.
|
||||
++ [NO] You don't make a bear. -> f4r4.first_duet
|
||||
--
|
||||
The wolf is watching you impatiently.
|
||||
++ [Make the bear bigger] You decide to make a bigger bear.
|
||||
++ [Do something else] -> f4r4.first_duet
|
||||
--
|
||||
But the beast on the screen beside you has clearly had enough and is readying its next attack.
|
||||
++ [Make your way to the back of the room]
|
||||
You can't compete from down here. So you start to make your way up the aisles to some high ground.
|
||||
|
||||
As soon as you step out of the light of the screen a voice comes from the back.
|
||||
|
||||
Her: No! Where are you going? I thought we were going to play?
|
||||
|
||||
You squint up and into the dark and see two abnormally large hands raised over the seats and backlit by the projector.
|
||||
|
||||
+++ [I'm just coming up to project better.]
|
||||
You: I'm just coming up to project better.
|
||||
|
||||
Her: I think you'll need a little more than that to get on my level.
|
||||
|
||||
++++ [I'm sure.] You: I'm sure.
|
||||
Her: You can still show me what you've got, though. Come here.
|
||||
-> f4r4.deathmatch
|
||||
++++ [We will see.] You: We will see.
|
||||
Her: OK. A challenge? IT. IS. ON.
|
||||
-> f4r4.deathmatch
|
||||
+++ [Who are you?]
|
||||
You: Who are you?
|
||||
|
||||
Her: Ähm, Hellooho? I'm just the greatest show master in this place. Maybe in the world. Your pleasure.
|
||||
|
||||
++++ [How was I supposed to know that?]
|
||||
~ favor_nails = favor_nails - 1
|
||||
|
||||
Her: OK I forgive you, that you have no taste. I'll even put on a show, yes? Sit down.
|
||||
|
||||
+++++ [Sit] You sit in the seat nearest to you. -> f4r4.the_show
|
||||
+++++ [Go up to her] You continue to go up to her. -> f4r4.the_performer
|
||||
+++++ [I thought we were going to make a wolf and bear death match?]
|
||||
You: I thought we were going to make a wolf and bear death match?
|
||||
Her: Ya. OK. You can come here so I can obliterate you.
|
||||
-> f4r4.deathmatch
|
||||
++++ [I think they have failed to put up a poster for you downstairs.]
|
||||
Her: What do you expect? There is no fudging respect for real art in this place. So lame. Sit down and I'm gonna give you the real off the hook showy show stuff.
|
||||
|
||||
+++++ [Sit] You sit in the seat nearest to you. -> f4r4.the_show
|
||||
+++++ [Go up to her] You continue to go up to her. -> f4r4.the_performer
|
||||
+++++ [I thought we were going to make a wolf and bear death match?]
|
||||
You: I thought we were going to make a wolf and bear death match?
|
||||
Her: Ya. OK. Come here and bring it on then.
|
||||
-> f4r4.deathmatch
|
||||
++ [Run to the back of the room]
|
||||
You are just a tiny thing down here, without a fighting chance. You bolt up the aisle to get yourself some size.
|
||||
|
||||
There is a startled squeak from the back of the room.
|
||||
|
||||
+++ [Ignore it]
|
||||
You charge on. To a spot where you will be big enough. You jump on to a seat for more high ground and make a much better sized bear.
|
||||
-> f4r4.bear
|
||||
+++ [Look up]
|
||||
You look up and slow down. Squinting into the darkness you can see a figure in one of the raised rows. Your opponent.
|
||||
|
||||
You arrive at a spot where you will be big enough. You jump on to a seat for more high ground and make a much better sized bear.
|
||||
-> f4r4.bear
|
||||
++ [Clamber over the seats like a crazy person]
|
||||
You are just a tiny thing down here, begging to be eaten alive. You jump and clamber over the backs of the seats to get yourself some size.
|
||||
|
||||
There is laughter from the back of the theatre.
|
||||
|
||||
Her: HAHAHA what are you doing? The wolf is not actually gonna eat you, you know?
|
||||
|
||||
+++ [Continue] You don't stop. Only slow down a little to look up.
|
||||
+++ [Halt] You stop to look up.
|
||||
---
|
||||
You can see two giant backlit hands. Forming a wolf.
|
||||
|
||||
+++ [Best to be sure it won't eat me.]
|
||||
Her: Jup. OK?
|
||||
|
||||
You have made enough ground and are standing up on a seat now. You make a bear that is much better sized.
|
||||
|
||||
You: Jup. Much better. Thanks. Let's GO.
|
||||
-> f4r4.bear
|
||||
+++ [I was just trying to get some high ground for a counterattack.]
|
||||
Her: Oh! You think you can challenge me? Bring it on.
|
||||
|
||||
You have made enough ground and are standing up on a seat now. You make a bear that is much better sized.
|
||||
|
||||
You: Let's go.
|
||||
-> f4r4.bear
|
||||
* [Mime your bloody death from the wolf attack]
|
||||
Your reaction time is a bit off. But it is not too late yet to mime your bloody murder by the claws of the beast. You put some theatricality into your death scene.
|
||||
|
||||
Your reward is amused laughter from the back of the theatre as the wolf dissolves into hands.
|
||||
|
||||
++ [Take a bow]
|
||||
You take a theatrical bow. Which is rewarded with some fluttery applause.
|
||||
++ [Try to see the shadow maker]
|
||||
You raise your hand and squint against the beamer's glare to see your attacker. You can only just about see two very large hands.
|
||||
--
|
||||
Her: Do another one. Do another one!
|
||||
|
||||
++ [Ok. Attack me again.] You: Ok. Attack me again. -> f4r4.unicorn
|
||||
++ [Make your own shadow puppet]
|
||||
You decide to try your own hand at shadow puppeteering.
|
||||
|
||||
Do you even know how to do that?
|
||||
|
||||
+++ [Yes]
|
||||
Alright. You make a...
|
||||
++++ [Wolf] A small wolf.
|
||||
++++ (bunny_puppet)[Bunny] A bunny.
|
||||
++++ [Butterfly] A butterfly.
|
||||
++++ [Spider] A scuttling spider.
|
||||
----
|
||||
Her: Sorry. Not sorry. I'm not impressed. But it is also veeeerrrrryyyy hard to impress me.
|
||||
+++ [No]
|
||||
You don't really know how but you can still make a shadow figure with your full body.
|
||||
|
||||
So you make...
|
||||
++++ [A worm] A worm, by raising your hand over your head and making a wave with your whole body.
|
||||
++++ (kitty)[Yourself cat ears]
|
||||
Your hands into cat ears. They turn and twitch.
|
||||
+++++ [Miou.]
|
||||
~ favor_nails = favor_nails + 1
|
||||
You: Miou.
|
||||
Her: Naw
|
||||
++++ [Yourself antlers] Your hands into majestic antlers protruding from your head.
|
||||
++++ [A T-pose] A T-pose. You had other ideas, but I guess you like being trash.
|
||||
Her: Hmmm. OK? What are you supposed to be?
|
||||
----
|
||||
---
|
||||
Her: I possibly see the tiniest bit of talent here. Maybe if you work at it, we could make a show together. If you try to upstage me, you are out, though.
|
||||
-> f4r4.apprentice
|
||||
* [Try to replicate the wolf]
|
||||
You try to figure out how exactly the shape of the wolf is made and to interlock your own fingers to replicate the effect.
|
||||
|
||||
++ [Observe] You observe...
|
||||
--
|
||||
++ [Build the finger form] Arrange the fingers...
|
||||
--
|
||||
++ [Project]
|
||||
// TODO: per Shuffle zufällig zwischen "passable" und "ugly" wechseln.
|
||||
// Inklecate unterstützt keine mehrzeiligen {~}-Sequenzen -- braucht
|
||||
// VAR wolf_ugly + { shuffle: - ... - ... }-Syntax oder zwei Pfade.
|
||||
And... you create a passable attempt. Much smaller and a little crooked, maybe.
|
||||
--
|
||||
|
||||
= unicorn
|
||||
Her: OK. OK. Do this one.
|
||||
|
||||
The hands resolve themselves into the shape of a unicorn.
|
||||
|
||||
+ [Try to pacify the beast]
|
||||
You hold out your hand in an attempt to pacify the beast.
|
||||
|
||||
The unicorn huffs and scuffs a hoof against the floor.
|
||||
|
||||
++ [Shhh. Easy. It's fine.]
|
||||
The unicorn doesn't seem convinced. It shakes its head in irritation.
|
||||
|
||||
+++ {snaks} [Present the snacks you picked up as a peace offering]
|
||||
You: Here I have something for you.
|
||||
|
||||
You hold out the sweets you picked up earlier.
|
||||
|
||||
Her: OMG! You have snacks? I want some. I want some! Get up here!
|
||||
|
||||
She sounds so excited, you have to oblige.
|
||||
-> f4r4.snacksharing
|
||||
+++ [Persist in being soothing]
|
||||
You still persist in making soothing noises and hold your hands up.
|
||||
|
||||
But it doesn't help.
|
||||
+++ [Prepare for an attack]
|
||||
You step back a little and square your stance for the next attack.
|
||||
---
|
||||
++ [Prepare for an attack]
|
||||
You step back a little and square your stance for the next attack.
|
||||
--
|
||||
+ [Prepare for the attack]
|
||||
You square your stance in anticipation for the next attack.
|
||||
-
|
||||
It scuffs its hoofs, rears up and charges you.
|
||||
|
||||
+ [Dodge]
|
||||
In the last moment you perform a jump-roll to the side.
|
||||
|
||||
Her: Hey! You can't dodge-roll in two dimensions!
|
||||
|
||||
++ [Worth a try!]
|
||||
You: Worth a try!
|
||||
|
||||
Now you are in the position of squaring off with the unicorn again. It charges once more.
|
||||
|
||||
+++ [Dodge]
|
||||
You dodge-roll again and hear a frustrated shout from the back of the theatre.
|
||||
|
||||
As you get back up again you see the shadow unicorn is already on top of you. Trampling you to death instead.
|
||||
|
||||
Her: There, serves you right, you jumping Jack. Are you trying to steal my scene? You know, for a moment there I thought this was kind of fun. Tell me: what are your acting references?
|
||||
++++ [Get impaled]
|
||||
---
|
||||
---
|
||||
++ [Oh Yeah? Watch me!]
|
||||
You: OH Yeah? Watch me!
|
||||
|
||||
You proceed to dodge-roll across the stage like a hedgehog on speed.
|
||||
|
||||
Between rolls you see the bewildered unicorn watch you from the side.
|
||||
|
||||
+++ [You could probably surprise it and boop it on the nose]
|
||||
~ boop_success = RANDOM(0, 1) == 1
|
||||
{boop_success:
|
||||
You jump-roll towards it, get up and manage a quick boop on its nose before dodge-rolling away again.
|
||||
- else:
|
||||
You jump-roll towards it. But unfortunately it wasn't that distracted and before you can boop it, it has lowered its horn and is making you into crazy-person-shashlik.
|
||||
}
|
||||
{not boop_success: -> after_boop}
|
||||
++++[Celebrate your victory]
|
||||
You stop to raise your hands up into the air to celebrate your victory. Meanwhile there is sarcastic clapping from the backseats.
|
||||
|
||||
Her: wow. OK. Great show, I guess. Very... acrobatic
|
||||
---- (after_boop)
|
||||
+++ [Continue rolling]
|
||||
You can't stop now. So you dodge-roll until you become nauseous and you are out of breath.
|
||||
|
||||
You just collapse on the floor and take some deep breaths. It's getting warm in the beamer light.
|
||||
|
||||
On the screen beside you, you can see the shadow unicorn come up to you cautiously and sniff you.
|
||||
|
||||
++++ [Raise a hand]
|
||||
You raise a hand to show up on the screen. The unicorn raises its head out of reach again.
|
||||
|
||||
Her: OH Good. You are not dead.
|
||||
++++ [Sit up]
|
||||
You sit up and the unicorn steps back from your shadow. Satisfied you are ok.
|
||||
|
||||
Her: Oh Good. You are not dead.
|
||||
----
|
||||
---
|
||||
++ [Just wanted to make it a bit more interesting.]
|
||||
You: Just wanted to make it a bit more interesting.
|
||||
|
||||
Her: Convince me of your acting talent first. We can workshop it once I have decided you are worth it to share a stage with me.
|
||||
|
||||
The unicorn charges again.
|
||||
|
||||
+++ [Dodge]
|
||||
You dodge-roll again.
|
||||
|
||||
Her: HEY!
|
||||
|
||||
As you get back up again you see the shadow unicorn is already on top of you. Trampling you to death.
|
||||
|
||||
Her: There, serves you right, you jumping Jack. Are you trying to steal my scene? Tell me: what are your acting references?
|
||||
++++ [Get impaled]
|
||||
---
|
||||
--
|
||||
You try even harder for your second death scene. Now that you know you have an appreciative scene partner and audience. You cheat to the side of the screen to see the unicorn and its movement. It has got you through the solar plexus and you convulse with the jerks of its head. When it finally pulls its horn up and out, you go with the movement and fall face first to the floor. You just about manage to make it a stage fall and not knock your teeth out on the thin scratchy carpet.
|
||||
|
||||
You stay on the floor until you hear clapping.
|
||||
|
||||
Her: I have to say, that wasn't bad. I mean I clearly carried the scene. But it really makes the action more lively with an acting partner. Tell me: what are your references?
|
||||
|
||||
* [I did some acting in school.] You: I did some acting in school.
|
||||
Her: Wouldn't go boasting with that.
|
||||
* [I actually have a degree in acting.] You: I actually have a degree in acting.
|
||||
Her: Well, there is no such thing as a legitimate art adjudication, you know.
|
||||
* [I am just a natural.] You: I am just a natural.
|
||||
Her: Ha. Sure thing sweety. Don't talk about a natural when I'm at the table.
|
||||
-
|
||||
Her: I can accept this as a first audition, I think. I know I'm hard to impress. The competition is very absent, though. But I think I really saw a bit of style in there. Deep, deep down. Maybe I can make a passable partner of you. Yas. I totally see the vision.
|
||||
-> f4r4.snacksharing
|
||||
|
||||
= snacksharing
|
||||
You go up the aisle towards the dark silhouette. It is too dark to make much out. When you are a few rows away you see the form move. You see arms and shoulders and above them something which is not the shape of a head.
|
||||
|
||||
When you are at her row, she is just stepping out onto the aisle. Her high-heeled shoes make no sound on the carpet. She straightens her frilly dress and puts one of the straps that slipped off her shoulder back in its rightful place.
|
||||
|
||||
When you look up to her face, your eyes adjust enough for you to see. A mess of long fingers unfurls itself into the clear shape of two gigantic but delicate hands, bedecked in rings and acrylic nails, growing from a slender neck with a y2K choker.
|
||||
|
||||
+ [I think she is cool.]
|
||||
She is very cool indeed.
|
||||
|
||||
You hold out the snacks.
|
||||
|
||||
Her: Yummy. Oh maaaan it's been tooooo long.
|
||||
|
||||
She grabs the package from your hand.
|
||||
|
||||
Her: I don't know. I guess I've just been so focused on perfecting my performance lately. I guess I haven't really had any time to get out. And walking up and down all the stairs. Urgh. Hate that.
|
||||
|
||||
One of her hands faces to the front and forms a snout with three fingers to the front while the Pinky and index finger perk up. Her makeshift foxface looks down to the package of snacks.
|
||||
|
||||
{had_sweets:
|
||||
Her: MMHHhhh Yum! Sweets. I always need something sweet.
|
||||
|
||||
She tears at the plastic wrapping and tosses a piece up into her hand which folds into a fist briefly. A second later, when her hand resumes the foxface the piece of candy is gone.
|
||||
|
||||
Her: Hm. OK. Ordinary candy.
|
||||
|
||||
She studies the package again.
|
||||
|
||||
Her: Didn't even know they had those here. Sweet. But that's it. You should try some of the local sorts. They pack a little more ZING.
|
||||
- else:
|
||||
Her: Huh. Salty snacks. OK I guess. Sorry, I normally prefer sweets. But I'd still like to try some.
|
||||
|
||||
She tears at the plastic wrapping and tosses a piece up into her hand which folds into a fist briefly. A second later, when her hand resumes the foxface the snack is gone.
|
||||
|
||||
Her: Hm? Is it just a normal snack?
|
||||
|
||||
She studies the package again.
|
||||
|
||||
Her: Didn't even know they had those here. You should try some of the local stuff. It's a little more interesting.
|
||||
}
|
||||
+ [I think she is scary.]
|
||||
You are a little intimidated.
|
||||
|
||||
Her: What? Don't worry, I don't bite. Well, nah you've seen the worst of my bite down there during the show.
|
||||
|
||||
One of her hands faces to the front and forms a snout with three fingers to the front while the Pinky and index finger perk up. She opens and closes the fingers a little as she talks. It looks like someone comically ventriloquising a hand puppet. It doesn't look genuine.
|
||||
|
||||
Her: Or maybe I will bite if you keep these sweets you promised me.
|
||||
|
||||
You raise your hand with the package and she snatches it from you. Her makeshift foxface looks down at the bag of snacks.
|
||||
|
||||
{had_sweets:
|
||||
Her: MMHHhhh Yum! Sweets. I always need something sweet.
|
||||
|
||||
She tears at the plastic wrapping and tosses a piece up into her hand which folds into a fist briefly. A second later, when her hand resumes the foxface the piece of candy is gone.
|
||||
|
||||
Her: Hm. OK. Ordinary candy.
|
||||
|
||||
She studies the package again.
|
||||
|
||||
Her: Didn't even know they had those here. Sweet. But that's it. You should try some of the local sorts. They pack a little more ZING.
|
||||
- else:
|
||||
Her: Huh. Salty snacks. OK I guess. I normally prefer sweets. But I guess this is a total emergency situation.
|
||||
|
||||
She tears at the plastic wrapping and tosses a piece up into her hand which folds into a fist briefly. A second later, when her hand resumes the foxface the snack is gone.
|
||||
|
||||
Her: Hm? Is it just a normal snack?
|
||||
|
||||
She studies the package again.
|
||||
|
||||
Her: Didn't even know they had those here. You should try some of the local stuff. It's a little more interesting.
|
||||
}
|
||||
-
|
||||
-> END
|
||||
|
||||
= deathmatch
|
||||
Her: If you are a grizzly bear, then I will be a dragon.
|
||||
|
||||
You: Then I will be the winter wind. Blowing out your flame and freezing you.
|
||||
|
||||
Her: Then I will be the ocean. Then you could never blow away.
|
||||
|
||||
You: My wind will be waves then. Sitting on top of you. Disturbing your depths.
|
||||
|
||||
Her: I will be the moon then. So you will have to bow to my every will.
|
||||
|
||||
+ [Flirt... yes please]
|
||||
~ favor_nails = favor_nails + 1
|
||||
+ [I will be the earth so you can only be as bright as I want you to be.]
|
||||
|
||||
-
|
||||
Her: Then I am the sun. So only I can decide where the light is.
|
||||
|
||||
The screen blacks out except for a small globe of light.
|
||||
-> END
|
||||
|
||||
= bear
|
||||
The wolf is snarling and fierce as before, but its tail twitches a little. You have intimidated it, by the looks of it.
|
||||
|
||||
Your bear is on its hind legs and looming tall.
|
||||
|
||||
You must never underestimate how dangerous a desperate animal is.
|
||||
|
||||
+ [Check your stance.]
|
||||
You are standing a bit unsafely on the chairs. So, you take your eyes off your opponent for a second to check your stance.
|
||||
|
||||
As you shift your right foot into a safer position the projector light on your cheek is disturbed. You can see out of the corner of your eye how the shadow grazes you. As you snap your attention up to the screen you see the wolf on your bear's hump. Biting down savagely.
|
||||
|
||||
You don't even have to think about it, as you raise your paws and beat at the side of the wolf. The force of it is enough to send it flying off of you and skidding to the edge of the screen. It is up on its feet again in seconds.
|
||||
|
||||
++ [Roar at it.]
|
||||
Your bear is not very hurt but furious. You send out a roar. A warning and a threat.
|
||||
|
||||
But the wolf will not back down. It charges again.
|
||||
++ [Run to it to attack.]
|
||||
You have no time to lose either, so you jump from the seat. The bear gallops towards the wolf.
|
||||
--
|
||||
++ [Bite] The bear is quick on four feet and opens its massive jaws to welcome the wolf.
|
||||
++ [Thrash] The bear stands tall and swipes a paw at the beast.
|
||||
--
|
||||
The wolf has left itself enough of an opening to jump back in time.
|
||||
|
||||
++ [Bite] You bite. But it jumps over you.
|
||||
++ [Thrash] You thrash. But it jumps around you.
|
||||
--
|
||||
You try a few more attacks. But the wolf is careful and nimble now.
|
||||
|
||||
It is baiting you. Cunning. You can already start to feel the strength of your attacks receding.
|
||||
|
||||
+ [Start your first attack]
|
||||
You are ready.
|
||||
|
||||
++ [Swipe a paw at the wolf]
|
||||
As the wolf jumps toward you the bear stands tall and swipes a massive paw down.
|
||||
|
||||
The wolf is quick but not quick enough to escape the attack completely. You catch it across its back and its jowls as it jumps away again.
|
||||
++ [Bite at the wolf.]
|
||||
The wolf jumps forward and the bear comes down to all four legs to bite at it.
|
||||
|
||||
The wolf is quick and jumps back from the bear's teeth, which scrape its back but don't manage the catch. A pained howling is your only reward.
|
||||
--
|
||||
The wolf paces for a moment.
|
||||
|
||||
++ [Wait] You wait to see its next move.
|
||||
Then it jumps forward again, jaws snapping.
|
||||
|
||||
+++ [Bite] The bear snaps its jaws right back. But the wolf has left itself enough of an opening to jump back in time.
|
||||
+++ [Thrash] But the bear is ready and strikes. The wolf has left itself enough of an opening to jump back in time.
|
||||
+++ [Dodge]
|
||||
~ fallbear = RANDOM(0, 1) == 1
|
||||
{fallbear:
|
||||
You only have to take a step back to be out of reach. But it only takes a step to destabilise you. And you fall.
|
||||
- else:
|
||||
It is slower so, the bear can just step back out of reach.
|
||||
}
|
||||
{not fallbear: -> after_dodge_inner}
|
||||
You can mostly brake your fall by catching the seats. You hang awkwardly in the row.
|
||||
|
||||
++++ [Admit defeat]
|
||||
You: Well played.
|
||||
|
||||
Her: Are you giving up?
|
||||
|
||||
You: What can I say. You got me.
|
||||
|
||||
You disentangle yourself from the seats. The wolf meanwhile resolves itself into a set of triumphant hands.
|
||||
|
||||
Her: Haha. Yes I did. But don't be embarrassed, I can tell you! You totally never stood a chance. I am the master of the craft. You wanna see some of my other specialities?
|
||||
|
||||
+++++ [Yes] You: Yes -> f4r4.her_portfolio
|
||||
++++ [Get back up]
|
||||
You quickly disentangle yourself from your compromised position. The wolf is already sniffing interestedly at the spot where you disappeared. Hungry for its kill.
|
||||
|
||||
++++ [Turn into a crocodile to ambush it]
|
||||
It is right above you now. An easy kill for an ambush predator. Your arms become the strong, crushing maw of a crocodile in a second. Jumping up as if from the watering hole. Swallowing the front half of the wolf in one bite. There isn't even enough time for it to scream in pain. You have become a swift and merciless death.
|
||||
|
||||
You slide back to the depths. Drawing the carcass of the wolf along.
|
||||
|
||||
Her: That was sooooooo not fair!
|
||||
|
||||
She sounds a little out of breath.
|
||||
|
||||
You: Well nature isn't fair.
|
||||
|
||||
Her: You shape shifted. That has nothing to do with nature.
|
||||
|
||||
You: It's called a plot twist.
|
||||
|
||||
Her: Ya a bad one. And what even happened to the bear? This isn't very narratively satisfying.
|
||||
|
||||
You: I'm sure the crocodile is satisfied.
|
||||
|
||||
Her: Oh my god. Just because you wanted to win. This makes no sense whatsoever!? If you could be bothered to stick to a storyline I would have destroyed you.
|
||||
|
||||
+++++ [OK. I'm sorry.]
|
||||
Her: Ya u better be. Now sit your ass down and I'm gonna show you how it's done.
|
||||
-> f4r4.the_show
|
||||
+++++ [You might have. If you were more flexible.]
|
||||
Her: Ah!? What did you just say? You can accuse me of many things but this is just slander! How dare you.
|
||||
-----
|
||||
---- (after_dodge_inner)
|
||||
---
|
||||
++ [Attack]
|
||||
But yours will be the next move.
|
||||
|
||||
+++ [Bite] The bear comes forward to snap its jaws. But the wolf has left itself enough of an opening to jump back in time.
|
||||
+++ [Thrash] The bear is ready and strikes. The wolf has left itself enough of an opening to jump back in time.
|
||||
---
|
||||
--
|
||||
You try a few more attacks. But the wolf is careful and nimble now.
|
||||
|
||||
++ [Bite] You bite. But it jumps over you.
|
||||
++ [Thrash] You thrash. But it jumps around you.
|
||||
--
|
||||
It is baiting you. Cunning. You can already start to feel the strength of your attacks receding.
|
||||
|
||||
= cbear
|
||||
// TODO: Gegenangriff-Bär -- Bär greift aus erhöhter Position an
|
||||
-> END
|
||||
|
||||
= the_show
|
||||
// TODO: Die eigentliche Vorstellung -- Nails' Schattentheater
|
||||
// Übergang in silent_show (stilles Beobachten) oder interaktiv
|
||||
-> silent_show
|
||||
|
||||
= silent_show
|
||||
A second shadowy hand joined the first. They wrap around each other and devolve into a mass of darkness with pins of light, shining between the bends of fingers which you no longer can tell apart or identify.
|
||||
|
||||
The splotch bulges and warps. Forming shapes you almost recognise. Organic shapes. No symmetry or clean edges.
|
||||
|
||||
Does your brain try to identify and categorise the shapes?
|
||||
|
||||
Do you see the pattern formed by projector light in the middle? Is it the fur pattern of an imaginary animal?
|
||||
|
||||
Do you see landscapes or stars?
|
||||
|
||||
The profiles of faces you know but can't place crown the edges of the shadow. There and gone in a moment.
|
||||
|
||||
Are you trying to figure out if the curtains are blue? There are no curtains. And if there were it would be rude to figure out the colour. Don't be distracted. Focus on the show. Someone is performing for you here.
|
||||
|
||||
The ink on the screen devolves itself into a shape your brain can grasp easily now. It is a Swan. Swimming on the water. It is perfect. Wings folded. The body smooth as a pebble. The neck long and arcing, bending up and around and down.
|
||||
|
||||
It dips its head into the water, gracefully. Kissing the ripples of the surface demurely before pulling back and shaking off water.
|
||||
|
||||
-> END
|
||||
|
||||
= shadow_battle
|
||||
// TODO: Schattenkampf -- Nails' wolf vs. Spieler's bear, freie Wahl
|
||||
-> END
|
||||
|
||||
= apprentice
|
||||
// TODO: Nails nimmt Spieler als Lehrling an
|
||||
-> END
|
||||
|
||||
= quitter
|
||||
// TODO: Hase flüchtet vom Bildschirm -- feige Niederlage
|
||||
-> END
|
||||
|
||||
= her_portfolio
|
||||
// TODO: Nails zeigt ihr Shadow-Portfolio
|
||||
-> END
|
||||
|
||||
= performance
|
||||
// TODO: Nails' volle Vorstellung
|
||||
-> END
|
||||
|
||||
= practice
|
||||
// TODO: Nails übt neue Figuren, Spieler schaut zu
|
||||
-> END
|
||||
|
||||
= seeulater
|
||||
// TODO: Verabredung für später -- Spieler kommt nochmal
|
||||
-> END
|
||||
@@ -10,7 +10,7 @@ VAR quest_mushroom_done = false
|
||||
|
||||
- quest_mushroom_active && mushrooms_collected >= 10:
|
||||
# color whisper
|
||||
Du hast {mushrooms_collected} Pilze dabei — mehr als genug!
|
||||
Du hast {mushrooms_collected} Pilze dabei -- mehr als genug!
|
||||
* [Hier, nimm sie.]
|
||||
~ quest_mushroom_done = true
|
||||
# color whisper
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"inkVersion":21,"root":[[["ev",{"VAR?":"quest_mushroom_done"},"/ev",{"->":".^.b","c":true},{"b":["\n","#","^color whisper","/#","^Danke nochmal für die Pilze. Das Rezept ist fast fertig.","\n",{"->":"ende"},{"->":"0.4"},null]}],["ev",{"VAR?":"quest_mushroom_active"},{"VAR?":"mushrooms_collected"},10,">=","&&","/ev",{"->":".^.b","c":true},{"b":["\n","#","^color whisper","/#","^Du hast ","ev",{"VAR?":"mushrooms_collected"},"out","/ev","^ Pilze dabei — mehr als genug!","\n","ev","str","^Hier, nimm sie.","/str","/ev",{"*":".^.c-0","flg":20},"ev","str","^Lass mich noch ein paar behalten.","/str","/ev",{"*":".^.c-1","flg":20},{"->":"0.4"},{"c-0":["\n","ev",true,"/ev",{"VAR=":"quest_mushroom_done","re":true},"#","^color whisper","/#","^Wunderbar, vielen Dank! Das wird ein gutes Süppchen.","\n",{"->":"ende"},{"#f":5}],"c-1":["\n","#","^color whisper","/#","^Wie du willst. Komm wieder, wenn du sie loswerden möchtest.","\n",{"->":"ende"},{"#f":5}]}]}],["ev",{"VAR?":"quest_mushroom_active"},"/ev",{"->":".^.b","c":true},{"b":["\n","#","^color whisper","/#","^Du hast schon ","ev",{"VAR?":"mushrooms_collected"},"out","/ev","^ von 10 Pilzen. Mach ruhig weiter.","\n",{"->":"ende"},{"->":"0.4"},null]}],[{"->":".^.b"},{"b":["\n","#","^color whisper","/#","^TEST1 Hallo! Ich bräuchte 10 Pilze für ein Rezept. Hättest du Lust, welche zu sammeln?","\n","ev","str","^Klar, ich helfe.","/str","/ev",{"*":".^.c-0","flg":20},"ev","str","^Vielleicht später.","/str","/ev",{"*":".^.c-1","flg":20},{"->":"0.4"},{"c-0":["\n","ev",true,"/ev",{"VAR=":"quest_mushroom_active","re":true},"#","^color whisper","/#","^Danke! Ich warte hier.","\n",{"->":"ende"},{"#f":5}],"c-1":["\n","#","^color whisper","/#","^Schade. Komm gerne nochmal vorbei.","\n",{"->":"ende"},{"#f":5}]}]}],"nop","\n",["done",{"#n":"g-0"}],null],"done",{"ende":[["ev","str","^Bis dann","/str","/ev",{"*":".^.c-0","flg":20},{"c-0":["\n","#","^person_farewell","/#","end",{"#f":5}]}],null],"global decl":["ev",0,{"VAR=":"mushrooms_collected"},false,{"VAR=":"quest_mushroom_active"},false,{"VAR=":"quest_mushroom_done"},"/ev","end",null]}],"listDefs":{}}
|
||||
{"inkVersion":21,"root":[[["ev",{"VAR?":"quest_mushroom_done"},"/ev",{"->":".^.b","c":true},{"b":["\n","#","^color whisper","/#","^Danke nochmal für die Pilze. Das Rezept ist fast fertig.","\n",{"->":"ende"},{"->":"0.4"},null]}],["ev",{"VAR?":"quest_mushroom_active"},{"VAR?":"mushrooms_collected"},10,">=","&&","/ev",{"->":".^.b","c":true},{"b":["\n","#","^color whisper","/#","^Du hast ","ev",{"VAR?":"mushrooms_collected"},"out","/ev","^ Pilze dabei -- mehr als genug!","\n","ev","str","^Hier, nimm sie.","/str","/ev",{"*":".^.c-0","flg":20},"ev","str","^Lass mich noch ein paar behalten.","/str","/ev",{"*":".^.c-1","flg":20},{"->":"0.4"},{"c-0":["\n","ev",true,"/ev",{"VAR=":"quest_mushroom_done","re":true},"#","^color whisper","/#","^Wunderbar, vielen Dank! Das wird ein gutes Süppchen.","\n",{"->":"ende"},{"#f":5}],"c-1":["\n","#","^color whisper","/#","^Wie du willst. Komm wieder, wenn du sie loswerden möchtest.","\n",{"->":"ende"},{"#f":5}]}]}],["ev",{"VAR?":"quest_mushroom_active"},"/ev",{"->":".^.b","c":true},{"b":["\n","#","^color whisper","/#","^Du hast schon ","ev",{"VAR?":"mushrooms_collected"},"out","/ev","^ von 10 Pilzen. Mach ruhig weiter.","\n",{"->":"ende"},{"->":"0.4"},null]}],[{"->":".^.b"},{"b":["\n","#","^color whisper","/#","^TEST1 Hallo! Ich bräuchte 10 Pilze für ein Rezept. Hättest du Lust, welche zu sammeln?","\n","ev","str","^Klar, ich helfe.","/str","/ev",{"*":".^.c-0","flg":20},"ev","str","^Vielleicht später.","/str","/ev",{"*":".^.c-1","flg":20},{"->":"0.4"},{"c-0":["\n","ev",true,"/ev",{"VAR=":"quest_mushroom_active","re":true},"#","^color whisper","/#","^Danke! Ich warte hier.","\n",{"->":"ende"},{"#f":5}],"c-1":["\n","#","^color whisper","/#","^Schade. Komm gerne nochmal vorbei.","\n",{"->":"ende"},{"#f":5}]}]}],"nop","\n",["done",{"#n":"g-0"}],null],"done",{"ende":[["ev","str","^Bis dann","/str","/ev",{"*":".^.c-0","flg":20},{"c-0":["\n","#","^person_farewell","/#","end",{"#f":5}]}],null],"global decl":["ev",0,{"VAR=":"mushrooms_collected"},false,{"VAR=":"quest_mushroom_active"},false,{"VAR=":"quest_mushroom_done"},"/ev","end",null]}],"listDefs":{}}
|
||||
@@ -1,5 +0,0 @@
|
||||
iasoidasd
|
||||
|
||||
|
||||
|
||||
jkhjkj
|
||||
@@ -1,284 +0,0 @@
|
||||
// Game: Generic
|
||||
// Format: Standard
|
||||
// entity 0
|
||||
{
|
||||
"classname" "worldspawn"
|
||||
"wad" ""
|
||||
// brush 0
|
||||
{
|
||||
( -112 -672 -80 ) ( -112 -720 -80 ) ( -112 -720 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
( -112 -720 -80 ) ( -64 -720 -80 ) ( -64 -720 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -720 -112 ) ( -64 -672 -112 ) ( -112 -672 -112 ) placeholder 64 224 0 0.2 0.2
|
||||
( -112 -672 -80 ) ( -64 -720 -80 ) ( -112 -720 -80 ) placeholder 64 224 0 0.2 0.2
|
||||
( -64 -672 -112 ) ( -64 -672 -96 ) ( -112 -672 -80 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -112 -672 -80 ) ( -64 -672 -96 ) ( -64 -720 -80 ) placeholder 64 224 0 0.2 0.2
|
||||
( -64 -720 -80 ) ( -64 -672 -96 ) ( -64 -672 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 1
|
||||
{
|
||||
( 0 -128 -16 ) ( 0 -127 -16 ) ( 0 -128 -15 ) placeholder 0 0 0 1 1
|
||||
( 0 -128 -16 ) ( 0 -128 -15 ) ( 1 -128 -16 ) placeholder -32 0 0 1 1
|
||||
( 0 -128 -16 ) ( 1 -128 -16 ) ( 0 -127 -16 ) placeholder -32 0 0 1 1
|
||||
( 64 -96 0 ) ( 64 -95 0 ) ( 65 -96 0 ) placeholder -32 0 0 1 1
|
||||
( 64 -96 0 ) ( 65 -96 0 ) ( 64 -96 1 ) placeholder -32 0 0 1 1
|
||||
( 64 -96 0 ) ( 64 -96 1 ) ( 64 -95 0 ) placeholder 0 0 0 1 1
|
||||
}
|
||||
// brush 2
|
||||
{
|
||||
( 64 -144 -32 ) ( 64 -143 -32 ) ( 64 -144 -31 ) placeholder -16 -16 0 1 1
|
||||
( 64 -144 -32 ) ( 64 -144 -31 ) ( 65 -144 -32 ) placeholder -32 -16 0 1 1
|
||||
( 64 -144 -32 ) ( 65 -144 -32 ) ( 64 -143 -32 ) placeholder -32 16 0 1 1
|
||||
( 192 -80 -16 ) ( 192 -79 -16 ) ( 193 -80 -16 ) placeholder -32 16 0 1 1
|
||||
( 192 -80 -16 ) ( 193 -80 -16 ) ( 192 -80 -15 ) placeholder -32 -16 0 1 1
|
||||
( 192 -80 -16 ) ( 192 -80 -15 ) ( 192 -79 -16 ) placeholder -16 -16 0 1 1
|
||||
}
|
||||
// brush 3
|
||||
{
|
||||
( 80 16 32 ) ( 80 15 32 ) ( 80 16 31 ) placeholder 0 -16 180 1 1
|
||||
( 144 -96 16 ) ( 145 -96 16 ) ( 144 -96 15 ) placeholder 16 -16 0 1 -1
|
||||
( 144 -96 16 ) ( 144 -97 16 ) ( 145 -96 16 ) placeholder 16 0 0 1 -1
|
||||
( 80 16 32 ) ( 81 16 32 ) ( 80 15 32 ) placeholder 16 0 0 1 -1
|
||||
( 80 16 32 ) ( 80 16 31 ) ( 81 16 32 ) placeholder 16 -16 0 1 -1
|
||||
( 144 -96 16 ) ( 144 -96 15 ) ( 144 -97 16 ) placeholder 0 -16 180 1 1
|
||||
}
|
||||
// brush 4
|
||||
{
|
||||
( 224 -48 -80 ) ( 224 16 -80 ) ( 224 -96 -56 ) placeholder -128 -60 0 1 2
|
||||
( 224 -96 -56 ) ( 288 -96 -80 ) ( 224 -48 -80 ) placeholder -32 8 0 1 -2
|
||||
( 224 16 -80 ) ( 272 -32 -56 ) ( 224 -96 -56 ) placeholder -134.26993 -44.949593 0 1 2
|
||||
( 272 -32 -56 ) ( 288 -96 -40 ) ( 224 -96 -56 ) placeholder -32 112 0 1 1
|
||||
( 224 -96 -56 ) ( 288 -96 -40 ) ( 288 -96 -80 ) placeholder -32 -16.941177 0 1 -2
|
||||
( 288 -96 -80 ) ( 288 16 -80 ) ( 224 16 -80 ) placeholder -32 144 0 1 1
|
||||
( 272 -32 -56 ) ( 304 -32 -56 ) ( 288 -96 -40 ) placeholder -32 112 0 1 1
|
||||
( 288 16 -80 ) ( 304 -32 -56 ) ( 272 -32 -56 ) placeholder -32 -24 0 1 -2
|
||||
( 288 -96 -80 ) ( 304 -32 -56 ) ( 288 16 -80 ) placeholder 144 -52 0 1 2
|
||||
( 288 -96 -40 ) ( 304 -32 -56 ) ( 288 -96 -80 ) placeholder 118.95169 -69.728745 0 1 2
|
||||
}
|
||||
// brush 5
|
||||
{
|
||||
( -144 -560 -80 ) ( -144 -559 -80 ) ( -144 -560 -79 ) placeholder -80 96 0 0.2 0.2
|
||||
( -144 -320 -80 ) ( -144 -320 -79 ) ( -143 -320 -80 ) placeholder -32 96 0 0.2 0.2
|
||||
( -144 -560 -80 ) ( -143 -560 -80 ) ( -144 -559 -80 ) placeholder -32 80 0 0.2 0.2
|
||||
( -16 -432 16 ) ( -16 -431 16 ) ( -15 -432 16 ) placeholder -32 80 0 0.2 0.2
|
||||
( -16 -304 -48 ) ( -15 -304 -48 ) ( -16 -304 -47 ) placeholder -32 96 0 0.2 0.2
|
||||
( 304 -432 -48 ) ( 304 -432 -47 ) ( 304 -431 -48 ) placeholder -80 96 0 0.2 0.2
|
||||
}
|
||||
// brush 6
|
||||
{
|
||||
( -160 -240 -16 ) ( -160 -241 -16 ) ( -160 -240 -17 ) placeholder -240 96 180 1 1
|
||||
( -64 -304 -32 ) ( -63 -304 -32 ) ( -64 -304 -33 ) placeholder 208 96 0 1 -1
|
||||
( -64 -352 -80 ) ( -64 -353 -80 ) ( -63 -352 -80 ) placeholder 208 240 0 1 -1
|
||||
( -160 -240 16 ) ( -159 -240 16 ) ( -160 -241 16 ) placeholder 208 240 0 1 -1
|
||||
( -160 192 -16 ) ( -160 192 -17 ) ( -159 192 -16 ) placeholder 208 96 0 1 -1
|
||||
( -144 -352 -32 ) ( -144 -352 -33 ) ( -144 -353 -32 ) placeholder -240 96 180 1 1
|
||||
}
|
||||
// brush 7
|
||||
{
|
||||
( 162.74516600406096 -112 -80 ) ( 163.4522727852475 -111.29289321881345 -80 ) ( 162.74516600406096 -112 -79 ) placeholder 190.39192 0 0 0.70710677 1
|
||||
( 162.74516600406096 -112 -80 ) ( 162.74516600406096 -112 -79 ) ( 163.45227278524752 -112.70710678118652 -80 ) placeholder -38.156433 0 0 0.70710677 1
|
||||
( 162.74516600406096 -112 -80 ) ( 163.45227278524752 -112.70710678118652 -80 ) ( 163.4522727852475 -111.29289321881345 -80 ) placeholder -2.27417 3.882248 315 1 1
|
||||
( 253.25483399593907 -112 -48 ) ( 253.96194077712562 -111.29289321881353 -48 ) ( 253.9619407771256 -112.70710678118664 -48 ) placeholder -2.27417 3.882248 315 1 1
|
||||
( 253.25483399593907 -112 -64 ) ( 253.25483399593907 -112 -63 ) ( 253.96194077712562 -111.29289321881353 -64 ) placeholder -6.156433 0 0 0.70710677 1
|
||||
( 253.25483399593907 -112 -64 ) ( 253.9619407771256 -112.70710678118664 -64 ) ( 253.25483399593907 -112 -63 ) placeholder 97.60808 0 180 0.70710677 -1
|
||||
}
|
||||
// brush 8
|
||||
{
|
||||
( -32 96 -80 ) ( -32 97 -80 ) ( -32 96 -79 ) placeholder 0 0 0 1 1
|
||||
( 96 272 -64 ) ( 97 272.625 -64 ) ( 96 272 -63 ) placeholder 0 0 0 1 1
|
||||
( -32 96 -80 ) ( -31 96.625 -80 ) ( -32 97 -80 ) placeholder -73.70788 15.668304 32.00539 1.1792476 0.87100387
|
||||
( 96 272 -32 ) ( 96 273 -32 ) ( 97 272.625 -32 ) placeholder -73.70788 15.668304 32.00539 1.1792476 0.87100387
|
||||
( -32 96 -80 ) ( -32 96 -79 ) ( -31 96.625 -80 ) placeholder 0 0 0 1 1
|
||||
( 96 272 -64 ) ( 96 272 -63 ) ( 96 273 -64 ) placeholder -80 0 0 1 1
|
||||
}
|
||||
// brush 9
|
||||
{
|
||||
( -64 -672 -96 ) ( -64 -720 -80 ) ( -64 -720 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
( -64 -672 -96 ) ( -16 -672 -80 ) ( -64 -720 -80 ) placeholder 80 224 0 0.2 0.2
|
||||
( -64 -720 -80 ) ( -16 -720 -80 ) ( -16 -720 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -720 -112 ) ( -16 -672 -112 ) ( -64 -672 -112 ) placeholder 80 224 0 0.2 0.2
|
||||
( -64 -720 -80 ) ( -16 -672 -80 ) ( -16 -720 -80 ) placeholder 80 224 0 0.2 0.2
|
||||
( -16 -672 -112 ) ( -16 -672 -80 ) ( -64 -672 -96 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -720 -80 ) ( -16 -672 -80 ) ( -16 -672 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 10
|
||||
{
|
||||
( -16 -672 -80 ) ( -16 -720 -80 ) ( -16 -720 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
( -16 -672 -80 ) ( 32 -672 -48 ) ( -16 -720 -80 ) placeholder -160 224 0 0.2 0.2
|
||||
( -16 -720 -80 ) ( 32 -720 -80 ) ( 32 -720 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -720 -80 ) ( 32 -672 -48 ) ( 32 -720 -80 ) placeholder -160 224 0 0.2 0.2
|
||||
( 32 -720 -112 ) ( 32 -672 -112 ) ( -16 -672 -112 ) placeholder -160 224 0 0.2 0.2
|
||||
( 32 -672 -112 ) ( 32 -672 -48 ) ( -16 -672 -80 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -720 -80 ) ( 32 -672 -48 ) ( 32 -672 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 11
|
||||
{
|
||||
( 32 -672 -48 ) ( 32 -720 -80 ) ( 32 -720 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
( 32 -720 -80 ) ( 80 -720 -80 ) ( 80 -720 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 80 -720 -80 ) ( 32 -720 -80 ) placeholder -144 224 0 0.2 0.2
|
||||
( 80 -720 -112 ) ( 80 -672 -112 ) ( 32 -672 -112 ) placeholder -144 224 0 0.2 0.2
|
||||
( 80 -672 -112 ) ( 80 -672 -80 ) ( 32 -672 -48 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 80 -672 -80 ) ( 80 -720 -80 ) placeholder -144 224 0 0.2 0.2
|
||||
( 80 -720 -80 ) ( 80 -672 -80 ) ( 80 -672 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 12
|
||||
{
|
||||
( -112 -624 -80 ) ( -112 -672 -80 ) ( -112 -672 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
( -112 -672 -80 ) ( -64 -672 -96 ) ( -64 -672 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -672 -112 ) ( -64 -624 -112 ) ( -112 -624 -112 ) placeholder 64 208 0 0.2 0.2
|
||||
( -112 -624 -80 ) ( -64 -624 -80 ) ( -112 -672 -80 ) placeholder 64 208 0 0.2 0.2
|
||||
( -64 -624 -112 ) ( -64 -624 -80 ) ( -112 -624 -80 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -112 -672 -80 ) ( -64 -624 -80 ) ( -64 -672 -96 ) placeholder 64 208 0 0.2 0.2
|
||||
( -64 -672 -96 ) ( -64 -624 -80 ) ( -64 -624 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 13
|
||||
{
|
||||
( -112 -384 -112 ) ( -112 -383 -112 ) ( -112 -384 -111 ) placeholder -192 -64 0 0.2 0.2
|
||||
( -112 -624 -112 ) ( -112 -624 -111 ) ( -111 -624 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -112 -384 -112 ) ( -111 -384 -112 ) ( -112 -383 -112 ) placeholder 64 192 0 0.2 0.2
|
||||
( 16 -256 -80 ) ( 16 -255 -80 ) ( 17 -256 -80 ) placeholder 64 192 0 0.2 0.2
|
||||
( 16 -576 -80 ) ( 17 -576 -80 ) ( 16 -576 -79 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -256 -80 ) ( -64 -256 -79 ) ( -64 -255 -80 ) placeholder -192 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 14
|
||||
{
|
||||
( -64 -624 -80 ) ( -64 -672 -96 ) ( -64 -672 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
( -64 -624 -80 ) ( -16 -672 -80 ) ( -64 -672 -96 ) placeholder 80 208 0 0.2 0.2
|
||||
( -64 -672 -96 ) ( -16 -672 -80 ) ( -16 -672 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -672 -112 ) ( -16 -624 -112 ) ( -64 -624 -112 ) placeholder 80 208 0 0.2 0.2
|
||||
( -64 -624 -80 ) ( -16 -624 -80 ) ( -16 -672 -80 ) placeholder 80 208 0 0.2 0.2
|
||||
( -16 -624 -112 ) ( -16 -624 -80 ) ( -64 -624 -80 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -672 -80 ) ( -16 -624 -80 ) ( -16 -624 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 15
|
||||
{
|
||||
( -112 -528 -80 ) ( -112 -576 -80 ) ( -112 -576 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
( -112 -528 -80 ) ( -64 -528 -48 ) ( -112 -576 -80 ) placeholder 64 176 0 0.2 0.2
|
||||
( -112 -576 -80 ) ( -64 -576 -80 ) ( -64 -576 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -112 -576 -80 ) ( -64 -528 -48 ) ( -64 -576 -80 ) placeholder 64 176 0 0.2 0.2
|
||||
( -64 -576 -112 ) ( -64 -528 -112 ) ( -112 -528 -112 ) placeholder 64 176 0 0.2 0.2
|
||||
( -64 -528 -112 ) ( -64 -528 -48 ) ( -112 -528 -80 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -576 -80 ) ( -64 -528 -48 ) ( -64 -528 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 16
|
||||
{
|
||||
( -64 -384 -112 ) ( -64 -383 -112 ) ( -64 -384 -111 ) placeholder -192 -64 0 0.2 0.2
|
||||
( -64 -624 -112 ) ( -64 -624 -111 ) ( -63 -624 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -64 -384 -112 ) ( -63 -384 -112 ) ( -64 -383 -112 ) placeholder 80 192 0 0.2 0.2
|
||||
( 64 -256 -80 ) ( 64 -255 -80 ) ( 65 -256 -80 ) placeholder 80 192 0 0.2 0.2
|
||||
( 64 -576 -80 ) ( 65 -576 -80 ) ( 64 -576 -79 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -256 -80 ) ( -16 -256 -79 ) ( -16 -255 -80 ) placeholder -192 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 17
|
||||
{
|
||||
( -16 -624 -80 ) ( -16 -672 -80 ) ( -16 -672 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
( -16 -624 -80 ) ( 32 -672 -48 ) ( -16 -672 -80 ) placeholder -160 208 0 0.2 0.2
|
||||
( -16 -672 -80 ) ( 32 -672 -48 ) ( 32 -672 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -672 -112 ) ( 32 -624 -112 ) ( -16 -624 -112 ) placeholder -160 208 0 0.2 0.2
|
||||
( -16 -624 -80 ) ( 32 -624 -80 ) ( 32 -672 -48 ) placeholder -160 208 0 0.2 0.2
|
||||
( 32 -624 -112 ) ( 32 -624 -80 ) ( -16 -624 -80 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 32 -624 -80 ) ( 32 -624 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 18
|
||||
{
|
||||
( -112 -480 -80 ) ( -112 -528 -80 ) ( -112 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -112 -480 -80 ) ( -64 -528 -48 ) ( -112 -528 -80 ) placeholder 64 160 0 0.2 0.2
|
||||
( -112 -528 -80 ) ( -64 -528 -48 ) ( -64 -528 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -528 -112 ) ( -64 -480 -112 ) ( -112 -480 -112 ) placeholder 64 160 0 0.2 0.2
|
||||
( -112 -480 -80 ) ( -64 -480 -80 ) ( -64 -528 -48 ) placeholder 64 160 0 0.2 0.2
|
||||
( -64 -480 -112 ) ( -64 -480 -80 ) ( -112 -480 -80 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -64 -480 -80 ) ( -64 -480 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 19
|
||||
{
|
||||
( -64 -528 -48 ) ( -64 -576 -80 ) ( -64 -576 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
( -64 -576 -80 ) ( -16 -576 -80 ) ( -16 -576 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -16 -576 -80 ) ( -64 -576 -80 ) placeholder 80 176 0 0.2 0.2
|
||||
( -16 -576 -112 ) ( -16 -528 -112 ) ( -64 -528 -112 ) placeholder 80 176 0 0.2 0.2
|
||||
( -16 -528 -112 ) ( -16 -528 -80 ) ( -64 -528 -48 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -16 -528 -80 ) ( -16 -576 -80 ) placeholder 80 176 0 0.2 0.2
|
||||
( -16 -576 -80 ) ( -16 -528 -80 ) ( -16 -528 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 20
|
||||
{
|
||||
( -16 -384 -112 ) ( -16 -383 -112 ) ( -16 -384 -111 ) placeholder -192 -64 0 0.2 0.2
|
||||
( -16 -624 -112 ) ( -16 -624 -111 ) ( -15 -624 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -384 -112 ) ( -15 -384 -112 ) ( -16 -383 -112 ) placeholder -160 192 0 0.2 0.2
|
||||
( 112 -256 -80 ) ( 112 -255 -80 ) ( 113 -256 -80 ) placeholder -160 192 0 0.2 0.2
|
||||
( 112 -576 -80 ) ( 113 -576 -80 ) ( 112 -576 -79 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -256 -80 ) ( 32 -256 -79 ) ( 32 -255 -80 ) placeholder -192 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 21
|
||||
{
|
||||
( 32 -624 -80 ) ( 32 -672 -48 ) ( 32 -672 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 80 -672 -80 ) ( 80 -672 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -672 -112 ) ( 80 -624 -112 ) ( 32 -624 -112 ) placeholder -144 208 0 0.2 0.2
|
||||
( 32 -624 -80 ) ( 80 -624 -80 ) ( 32 -672 -48 ) placeholder -144 208 0 0.2 0.2
|
||||
( 80 -624 -112 ) ( 80 -624 -80 ) ( 32 -624 -80 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 80 -624 -80 ) ( 80 -672 -80 ) placeholder -144 208 0 0.2 0.2
|
||||
( 80 -672 -80 ) ( 80 -624 -80 ) ( 80 -624 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 22
|
||||
{
|
||||
( -64 -480 -80 ) ( -64 -528 -48 ) ( -64 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -16 -528 -80 ) ( -16 -528 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -528 -112 ) ( -16 -480 -112 ) ( -64 -480 -112 ) placeholder 80 160 0 0.2 0.2
|
||||
( -64 -480 -80 ) ( -16 -480 -80 ) ( -64 -528 -48 ) placeholder 80 160 0 0.2 0.2
|
||||
( -16 -480 -112 ) ( -16 -480 -80 ) ( -64 -480 -80 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -16 -480 -80 ) ( -16 -528 -80 ) placeholder 80 160 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( -16 -480 -80 ) ( -16 -480 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 23
|
||||
{
|
||||
( -16 -528 -80 ) ( -16 -576 -80 ) ( -16 -576 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
( -16 -576 -80 ) ( 32 -576 -80 ) ( 32 -576 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -576 -112 ) ( 32 -528 -112 ) ( -16 -528 -112 ) placeholder -160 176 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( 32 -576 -80 ) ( -16 -576 -80 ) placeholder -160 176 0 0.2 0.2
|
||||
( 32 -528 -112 ) ( 32 -528 -96 ) ( -16 -528 -80 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( 32 -528 -96 ) ( 32 -576 -80 ) placeholder -160 176 0 0.2 0.2
|
||||
( 32 -576 -80 ) ( 32 -528 -96 ) ( 32 -528 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 24
|
||||
{
|
||||
( 32 -384 -112 ) ( 32 -383 -112 ) ( 32 -384 -111 ) placeholder -192 -64 0 0.2 0.2
|
||||
( 32 -624 -112 ) ( 32 -624 -111 ) ( 33 -624 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 32 -384 -112 ) ( 33 -384 -112 ) ( 32 -383 -112 ) placeholder -144 192 0 0.2 0.2
|
||||
( 160 -256 -80 ) ( 160 -255 -80 ) ( 161 -256 -80 ) placeholder -144 192 0 0.2 0.2
|
||||
( 160 -576 -80 ) ( 161 -576 -80 ) ( 160 -576 -79 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -256 -80 ) ( 80 -256 -79 ) ( 80 -255 -80 ) placeholder -192 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 25
|
||||
{
|
||||
( -16 -480 -80 ) ( -16 -528 -80 ) ( -16 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( 32 -528 -96 ) ( 32 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -528 -112 ) ( 32 -480 -112 ) ( -16 -480 -112 ) placeholder -160 160 0 0.2 0.2
|
||||
( -16 -480 -80 ) ( 32 -480 -80 ) ( -16 -528 -80 ) placeholder -160 160 0 0.2 0.2
|
||||
( 32 -480 -112 ) ( 32 -480 -80 ) ( -16 -480 -80 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( 32 -480 -80 ) ( 32 -528 -96 ) placeholder -160 160 0 0.2 0.2
|
||||
( 32 -528 -96 ) ( 32 -480 -80 ) ( 32 -480 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 26
|
||||
{
|
||||
( 32 -528 -96 ) ( 32 -576 -80 ) ( 32 -576 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
( 32 -528 -96 ) ( 80 -528 -80 ) ( 32 -576 -80 ) placeholder -144 176 0 0.2 0.2
|
||||
( 32 -576 -80 ) ( 80 -576 -80 ) ( 80 -576 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -576 -112 ) ( 80 -528 -112 ) ( 32 -528 -112 ) placeholder -144 176 0 0.2 0.2
|
||||
( 32 -576 -80 ) ( 80 -528 -80 ) ( 80 -576 -80 ) placeholder -144 176 0 0.2 0.2
|
||||
( 80 -528 -112 ) ( 80 -528 -80 ) ( 32 -528 -96 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -576 -80 ) ( 80 -528 -80 ) ( 80 -528 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 27
|
||||
{
|
||||
( 32 -480 -80 ) ( 32 -528 -96 ) ( 32 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -480 -80 ) ( 80 -528 -80 ) ( 32 -528 -96 ) placeholder -144 160 0 0.2 0.2
|
||||
( 32 -528 -96 ) ( 80 -528 -80 ) ( 80 -528 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -528 -112 ) ( 80 -480 -112 ) ( 32 -480 -112 ) placeholder -144 160 0 0.2 0.2
|
||||
( 32 -480 -80 ) ( 80 -480 -80 ) ( 80 -528 -80 ) placeholder -144 160 0 0.2 0.2
|
||||
( 80 -480 -112 ) ( 80 -480 -80 ) ( 32 -480 -80 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -528 -80 ) ( 80 -480 -80 ) ( 80 -480 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
}
|
||||
}
|
||||
// entity 1
|
||||
{
|
||||
"classname" "info_player_start"
|
||||
"origin" "112 -48 56"
|
||||
}
|
||||
@@ -1,293 +0,0 @@
|
||||
// Game: Generic
|
||||
// Format: Standard
|
||||
// entity 0
|
||||
{
|
||||
"classname" "worldspawn"
|
||||
"wad" ""
|
||||
// brush 0
|
||||
{
|
||||
( -112 -672 -80 ) ( -112 -720 -80 ) ( -112 -720 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
( -112 -720 -80 ) ( -64 -720 -80 ) ( -64 -720 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -720 -112 ) ( -64 -672 -112 ) ( -112 -672 -112 ) placeholder 64 224 0 0.2 0.2
|
||||
( -112 -672 -80 ) ( -64 -720 -80 ) ( -112 -720 -80 ) placeholder 64 224 0 0.2 0.2
|
||||
( -64 -672 -112 ) ( -64 -672 -96 ) ( -112 -672 -80 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -112 -672 -80 ) ( -64 -672 -96 ) ( -64 -720 -80 ) placeholder 64 224 0 0.2 0.2
|
||||
( -64 -720 -80 ) ( -64 -672 -96 ) ( -64 -672 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 1
|
||||
{
|
||||
( 0 -128 -16 ) ( 0 -127 -16 ) ( 0 -128 -15 ) placeholder 0 0 0 1 1
|
||||
( 0 -128 -16 ) ( 0 -128 -15 ) ( 1 -128 -16 ) placeholder -32 0 0 1 1
|
||||
( 0 -128 -16 ) ( 1 -128 -16 ) ( 0 -127 -16 ) placeholder -32 0 0 1 1
|
||||
( 64 -96 0 ) ( 64 -95 0 ) ( 65 -96 0 ) placeholder -32 0 0 1 1
|
||||
( 64 -96 0 ) ( 65 -96 0 ) ( 64 -96 1 ) placeholder -32 0 0 1 1
|
||||
( 64 -96 0 ) ( 64 -96 1 ) ( 64 -95 0 ) placeholder 0 0 0 1 1
|
||||
}
|
||||
// brush 2
|
||||
{
|
||||
( 64 -144 -32 ) ( 64 -143 -32 ) ( 64 -144 -31 ) placeholder -16 -16 0 1 1
|
||||
( 64 -144 -32 ) ( 64 -144 -31 ) ( 65 -144 -32 ) placeholder -32 -16 0 1 1
|
||||
( 64 -144 -32 ) ( 65 -144 -32 ) ( 64 -143 -32 ) placeholder -32 16 0 1 1
|
||||
( 192 -80 -16 ) ( 192 -79 -16 ) ( 193 -80 -16 ) placeholder -32 16 0 1 1
|
||||
( 192 -80 -16 ) ( 193 -80 -16 ) ( 192 -80 -15 ) placeholder -32 -16 0 1 1
|
||||
( 192 -80 -16 ) ( 192 -80 -15 ) ( 192 -79 -16 ) placeholder -16 -16 0 1 1
|
||||
}
|
||||
// brush 3
|
||||
{
|
||||
( 80 16 32 ) ( 80 15 32 ) ( 80 16 31 ) placeholder 0 -16 180 1 1
|
||||
( 144 -96 16 ) ( 145 -96 16 ) ( 144 -96 15 ) placeholder 16 -16 0 1 -1
|
||||
( 144 -96 16 ) ( 144 -97 16 ) ( 145 -96 16 ) placeholder 16 0 0 1 -1
|
||||
( 80 16 32 ) ( 81 16 32 ) ( 80 15 32 ) placeholder 16 0 0 1 -1
|
||||
( 80 16 32 ) ( 80 16 31 ) ( 81 16 32 ) placeholder 16 -16 0 1 -1
|
||||
( 144 -96 16 ) ( 144 -96 15 ) ( 144 -97 16 ) placeholder 0 -16 180 1 1
|
||||
}
|
||||
// brush 4
|
||||
{
|
||||
( 224 -48 -80 ) ( 224 16 -80 ) ( 224 -96 -56 ) placeholder -128 -60 0 1 2
|
||||
( 224 -96 -56 ) ( 288 -96 -80 ) ( 224 -48 -80 ) placeholder -32 8 0 1 -2
|
||||
( 224 16 -80 ) ( 272 -32 -56 ) ( 224 -96 -56 ) placeholder -134.26993 -44.949593 0 1 2
|
||||
( 272 -32 -56 ) ( 288 -96 -40 ) ( 224 -96 -56 ) placeholder -32 112 0 1 1
|
||||
( 224 -96 -56 ) ( 288 -96 -40 ) ( 288 -96 -80 ) placeholder -32 -16.941177 0 1 -2
|
||||
( 288 -96 -80 ) ( 288 16 -80 ) ( 224 16 -80 ) placeholder -32 144 0 1 1
|
||||
( 272 -32 -56 ) ( 304 -32 -56 ) ( 288 -96 -40 ) placeholder -32 112 0 1 1
|
||||
( 288 16 -80 ) ( 304 -32 -56 ) ( 272 -32 -56 ) placeholder -32 -24 0 1 -2
|
||||
( 288 -96 -80 ) ( 304 -32 -56 ) ( 288 16 -80 ) placeholder 144 -52 0 1 2
|
||||
( 288 -96 -40 ) ( 304 -32 -56 ) ( 288 -96 -80 ) placeholder 118.95169 -69.728745 0 1 2
|
||||
}
|
||||
// brush 5
|
||||
{
|
||||
( -144 -560 -80 ) ( -144 -559 -80 ) ( -144 -560 -79 ) placeholder -80 96 0 0.2 0.2
|
||||
( -144 -320 -80 ) ( -144 -320 -79 ) ( -143 -320 -80 ) placeholder -32 96 0 0.2 0.2
|
||||
( -144 -560 -80 ) ( -143 -560 -80 ) ( -144 -559 -80 ) placeholder -32 80 0 0.2 0.2
|
||||
( -16 -432 16 ) ( -16 -431 16 ) ( -15 -432 16 ) placeholder -32 80 0 0.2 0.2
|
||||
( -16 -304 -48 ) ( -15 -304 -48 ) ( -16 -304 -47 ) placeholder -32 96 0 0.2 0.2
|
||||
( 304 -432 -48 ) ( 304 -432 -47 ) ( 304 -431 -48 ) placeholder -80 96 0 0.2 0.2
|
||||
}
|
||||
// brush 6
|
||||
{
|
||||
( -160 -240 -16 ) ( -160 -241 -16 ) ( -160 -240 -17 ) placeholder -240 96 180 1 1
|
||||
( -64 -304 -32 ) ( -63 -304 -32 ) ( -64 -304 -33 ) placeholder 208 96 0 1 -1
|
||||
( -64 -352 -80 ) ( -64 -353 -80 ) ( -63 -352 -80 ) placeholder 208 240 0 1 -1
|
||||
( -160 -240 16 ) ( -159 -240 16 ) ( -160 -241 16 ) placeholder 208 240 0 1 -1
|
||||
( -160 192 -16 ) ( -160 192 -17 ) ( -159 192 -16 ) placeholder 208 96 0 1 -1
|
||||
( -144 -352 -32 ) ( -144 -352 -33 ) ( -144 -353 -32 ) placeholder -240 96 180 1 1
|
||||
}
|
||||
// brush 7
|
||||
{
|
||||
( 162.74516600406096 -112 -80 ) ( 163.4522727852475 -111.29289321881345 -80 ) ( 162.74516600406096 -112 -79 ) placeholder 190.39192 0 0 0.70710677 1
|
||||
( 162.74516600406096 -112 -80 ) ( 162.74516600406096 -112 -79 ) ( 163.45227278524752 -112.70710678118652 -80 ) placeholder -38.156433 0 0 0.70710677 1
|
||||
( 162.74516600406096 -112 -80 ) ( 163.45227278524752 -112.70710678118652 -80 ) ( 163.4522727852475 -111.29289321881345 -80 ) placeholder -2.27417 3.882248 315 1 1
|
||||
( 253.25483399593907 -112 -48 ) ( 253.96194077712562 -111.29289321881353 -48 ) ( 253.9619407771256 -112.70710678118664 -48 ) placeholder -2.27417 3.882248 315 1 1
|
||||
( 253.25483399593907 -112 -64 ) ( 253.25483399593907 -112 -63 ) ( 253.96194077712562 -111.29289321881353 -64 ) placeholder -6.156433 0 0 0.70710677 1
|
||||
( 253.25483399593907 -112 -64 ) ( 253.9619407771256 -112.70710678118664 -64 ) ( 253.25483399593907 -112 -63 ) placeholder 97.60808 0 180 0.70710677 -1
|
||||
}
|
||||
// brush 8
|
||||
{
|
||||
( -32 96 -80 ) ( -32 97 -80 ) ( -32 96 -79 ) placeholder 0 0 0 1 1
|
||||
( 96 272 -64 ) ( 97 272.625 -64 ) ( 96 272 -63 ) placeholder 0 0 0 1 1
|
||||
( -32 96 -80 ) ( -31 96.625 -80 ) ( -32 97 -80 ) placeholder -73.70788 15.668304 32.00539 1.1792476 0.87100387
|
||||
( 96 272 -32 ) ( 96 273 -32 ) ( 97 272.625 -32 ) placeholder -73.70788 15.668304 32.00539 1.1792476 0.87100387
|
||||
( -32 96 -80 ) ( -32 96 -79 ) ( -31 96.625 -80 ) placeholder 0 0 0 1 1
|
||||
( 96 272 -64 ) ( 96 272 -63 ) ( 96 273 -64 ) placeholder -80 0 0 1 1
|
||||
}
|
||||
// brush 9
|
||||
{
|
||||
( -64 -672 -96 ) ( -64 -720 -80 ) ( -64 -720 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
( -64 -672 -96 ) ( -16 -672 -80 ) ( -64 -720 -80 ) placeholder 80 224 0 0.2 0.2
|
||||
( -64 -720 -80 ) ( -16 -720 -80 ) ( -16 -720 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -720 -112 ) ( -16 -672 -112 ) ( -64 -672 -112 ) placeholder 80 224 0 0.2 0.2
|
||||
( -64 -720 -80 ) ( -16 -672 -80 ) ( -16 -720 -80 ) placeholder 80 224 0 0.2 0.2
|
||||
( -16 -672 -112 ) ( -16 -672 -80 ) ( -64 -672 -96 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -720 -80 ) ( -16 -672 -80 ) ( -16 -672 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 10
|
||||
{
|
||||
( -16 -672 -80 ) ( -16 -720 -80 ) ( -16 -720 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
( -16 -672 -80 ) ( 32 -672 -48 ) ( -16 -720 -80 ) placeholder -160 224 0 0.2 0.2
|
||||
( -16 -720 -80 ) ( 32 -720 -80 ) ( 32 -720 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -720 -80 ) ( 32 -672 -48 ) ( 32 -720 -80 ) placeholder -160 224 0 0.2 0.2
|
||||
( 32 -720 -112 ) ( 32 -672 -112 ) ( -16 -672 -112 ) placeholder -160 224 0 0.2 0.2
|
||||
( 32 -672 -112 ) ( 32 -672 -48 ) ( -16 -672 -80 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -720 -80 ) ( 32 -672 -48 ) ( 32 -672 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 11
|
||||
{
|
||||
( 32 -672 -48 ) ( 32 -720 -80 ) ( 32 -720 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
( 32 -720 -80 ) ( 80 -720 -80 ) ( 80 -720 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 80 -720 -80 ) ( 32 -720 -80 ) placeholder -144 224 0 0.2 0.2
|
||||
( 80 -720 -112 ) ( 80 -672 -112 ) ( 32 -672 -112 ) placeholder -144 224 0 0.2 0.2
|
||||
( 80 -672 -112 ) ( 80 -672 -80 ) ( 32 -672 -48 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 80 -672 -80 ) ( 80 -720 -80 ) placeholder -144 224 0 0.2 0.2
|
||||
( 80 -720 -80 ) ( 80 -672 -80 ) ( 80 -672 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 12
|
||||
{
|
||||
( -112 -624 -80 ) ( -112 -672 -80 ) ( -112 -672 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
( -112 -672 -80 ) ( -64 -672 -96 ) ( -64 -672 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -672 -112 ) ( -64 -624 -112 ) ( -112 -624 -112 ) placeholder 64 208 0 0.2 0.2
|
||||
( -112 -624 -80 ) ( -64 -624 -80 ) ( -112 -672 -80 ) placeholder 64 208 0 0.2 0.2
|
||||
( -64 -624 -112 ) ( -64 -624 -80 ) ( -112 -624 -80 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -112 -672 -80 ) ( -64 -624 -80 ) ( -64 -672 -96 ) placeholder 64 208 0 0.2 0.2
|
||||
( -64 -672 -96 ) ( -64 -624 -80 ) ( -64 -624 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 13
|
||||
{
|
||||
( -112 -384 -112 ) ( -112 -383 -112 ) ( -112 -384 -111 ) placeholder -192 -64 0 0.2 0.2
|
||||
( -112 -624 -112 ) ( -112 -624 -111 ) ( -111 -624 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -112 -384 -112 ) ( -111 -384 -112 ) ( -112 -383 -112 ) placeholder 64 192 0 0.2 0.2
|
||||
( 16 -256 -80 ) ( 16 -255 -80 ) ( 17 -256 -80 ) placeholder 64 192 0 0.2 0.2
|
||||
( 16 -576 -80 ) ( 17 -576 -80 ) ( 16 -576 -79 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -256 -80 ) ( -64 -256 -79 ) ( -64 -255 -80 ) placeholder -192 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 14
|
||||
{
|
||||
( -64 -624 -80 ) ( -64 -672 -96 ) ( -64 -672 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
( -64 -624 -80 ) ( -16 -672 -80 ) ( -64 -672 -96 ) placeholder 80 208 0 0.2 0.2
|
||||
( -64 -672 -96 ) ( -16 -672 -80 ) ( -16 -672 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -672 -112 ) ( -16 -624 -112 ) ( -64 -624 -112 ) placeholder 80 208 0 0.2 0.2
|
||||
( -64 -624 -80 ) ( -16 -624 -80 ) ( -16 -672 -80 ) placeholder 80 208 0 0.2 0.2
|
||||
( -16 -624 -112 ) ( -16 -624 -80 ) ( -64 -624 -80 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -672 -80 ) ( -16 -624 -80 ) ( -16 -624 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 15
|
||||
{
|
||||
( -112 -528 -80 ) ( -112 -576 -80 ) ( -112 -576 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
( -112 -528 -80 ) ( -64 -528 -48 ) ( -112 -576 -80 ) placeholder 64 176 0 0.2 0.2
|
||||
( -112 -576 -80 ) ( -64 -576 -80 ) ( -64 -576 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -112 -576 -80 ) ( -64 -528 -48 ) ( -64 -576 -80 ) placeholder 64 176 0 0.2 0.2
|
||||
( -64 -576 -112 ) ( -64 -528 -112 ) ( -112 -528 -112 ) placeholder 64 176 0 0.2 0.2
|
||||
( -64 -528 -112 ) ( -64 -528 -48 ) ( -112 -528 -80 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -576 -80 ) ( -64 -528 -48 ) ( -64 -528 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 16
|
||||
{
|
||||
( -64 -384 -112 ) ( -64 -383 -112 ) ( -64 -384 -111 ) placeholder -192 -64 0 0.2 0.2
|
||||
( -64 -624 -112 ) ( -64 -624 -111 ) ( -63 -624 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -64 -384 -112 ) ( -63 -384 -112 ) ( -64 -383 -112 ) placeholder 80 192 0 0.2 0.2
|
||||
( 64 -256 -80 ) ( 64 -255 -80 ) ( 65 -256 -80 ) placeholder 80 192 0 0.2 0.2
|
||||
( 64 -576 -80 ) ( 65 -576 -80 ) ( 64 -576 -79 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -256 -80 ) ( -16 -256 -79 ) ( -16 -255 -80 ) placeholder -192 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 17
|
||||
{
|
||||
( -16 -624 -80 ) ( -16 -672 -80 ) ( -16 -672 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
( -16 -624 -80 ) ( 32 -672 -48 ) ( -16 -672 -80 ) placeholder -160 208 0 0.2 0.2
|
||||
( -16 -672 -80 ) ( 32 -672 -48 ) ( 32 -672 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -672 -112 ) ( 32 -624 -112 ) ( -16 -624 -112 ) placeholder -160 208 0 0.2 0.2
|
||||
( -16 -624 -80 ) ( 32 -624 -80 ) ( 32 -672 -48 ) placeholder -160 208 0 0.2 0.2
|
||||
( 32 -624 -112 ) ( 32 -624 -80 ) ( -16 -624 -80 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 32 -624 -80 ) ( 32 -624 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 18
|
||||
{
|
||||
( -112 -480 -80 ) ( -112 -528 -80 ) ( -112 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -112 -480 -80 ) ( -64 -528 -48 ) ( -112 -528 -80 ) placeholder 64 160 0 0.2 0.2
|
||||
( -112 -528 -80 ) ( -64 -528 -48 ) ( -64 -528 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -528 -112 ) ( -64 -480 -112 ) ( -112 -480 -112 ) placeholder 64 160 0 0.2 0.2
|
||||
( -112 -480 -80 ) ( -64 -480 -80 ) ( -64 -528 -48 ) placeholder 64 160 0 0.2 0.2
|
||||
( -64 -480 -112 ) ( -64 -480 -80 ) ( -112 -480 -80 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -64 -480 -80 ) ( -64 -480 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 19
|
||||
{
|
||||
( -64 -528 -48 ) ( -64 -576 -80 ) ( -64 -576 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
( -64 -576 -80 ) ( -16 -576 -80 ) ( -16 -576 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -16 -576 -80 ) ( -64 -576 -80 ) placeholder 80 176 0 0.2 0.2
|
||||
( -16 -576 -112 ) ( -16 -528 -112 ) ( -64 -528 -112 ) placeholder 80 176 0 0.2 0.2
|
||||
( -16 -528 -112 ) ( -16 -528 -80 ) ( -64 -528 -48 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -16 -528 -80 ) ( -16 -576 -80 ) placeholder 80 176 0 0.2 0.2
|
||||
( -16 -576 -80 ) ( -16 -528 -80 ) ( -16 -528 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 20
|
||||
{
|
||||
( -16 -384 -112 ) ( -16 -383 -112 ) ( -16 -384 -111 ) placeholder -192 -64 0 0.2 0.2
|
||||
( -16 -624 -112 ) ( -16 -624 -111 ) ( -15 -624 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -384 -112 ) ( -15 -384 -112 ) ( -16 -383 -112 ) placeholder -160 192 0 0.2 0.2
|
||||
( 112 -256 -80 ) ( 112 -255 -80 ) ( 113 -256 -80 ) placeholder -160 192 0 0.2 0.2
|
||||
( 112 -576 -80 ) ( 113 -576 -80 ) ( 112 -576 -79 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -256 -80 ) ( 32 -256 -79 ) ( 32 -255 -80 ) placeholder -192 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 21
|
||||
{
|
||||
( 32 -624 -80 ) ( 32 -672 -48 ) ( 32 -672 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 80 -672 -80 ) ( 80 -672 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -672 -112 ) ( 80 -624 -112 ) ( 32 -624 -112 ) placeholder -144 208 0 0.2 0.2
|
||||
( 32 -624 -80 ) ( 80 -624 -80 ) ( 32 -672 -48 ) placeholder -144 208 0 0.2 0.2
|
||||
( 80 -624 -112 ) ( 80 -624 -80 ) ( 32 -624 -80 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 80 -624 -80 ) ( 80 -672 -80 ) placeholder -144 208 0 0.2 0.2
|
||||
( 80 -672 -80 ) ( 80 -624 -80 ) ( 80 -624 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 22
|
||||
{
|
||||
( -64 -480 -80 ) ( -64 -528 -48 ) ( -64 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -16 -528 -80 ) ( -16 -528 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -528 -112 ) ( -16 -480 -112 ) ( -64 -480 -112 ) placeholder 80 160 0 0.2 0.2
|
||||
( -64 -480 -80 ) ( -16 -480 -80 ) ( -64 -528 -48 ) placeholder 80 160 0 0.2 0.2
|
||||
( -16 -480 -112 ) ( -16 -480 -80 ) ( -64 -480 -80 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -16 -480 -80 ) ( -16 -528 -80 ) placeholder 80 160 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( -16 -480 -80 ) ( -16 -480 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 23
|
||||
{
|
||||
( -16 -528 -80 ) ( -16 -576 -80 ) ( -16 -576 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
( -16 -576 -80 ) ( 32 -576 -80 ) ( 32 -576 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -576 -112 ) ( 32 -528 -112 ) ( -16 -528 -112 ) placeholder -160 176 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( 32 -576 -80 ) ( -16 -576 -80 ) placeholder -160 176 0 0.2 0.2
|
||||
( 32 -528 -112 ) ( 32 -528 -96 ) ( -16 -528 -80 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( 32 -528 -96 ) ( 32 -576 -80 ) placeholder -160 176 0 0.2 0.2
|
||||
( 32 -576 -80 ) ( 32 -528 -96 ) ( 32 -528 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 24
|
||||
{
|
||||
( 32 -384 -112 ) ( 32 -383 -112 ) ( 32 -384 -111 ) placeholder -192 -64 0 0.2 0.2
|
||||
( 32 -624 -112 ) ( 32 -624 -111 ) ( 33 -624 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 32 -384 -112 ) ( 33 -384 -112 ) ( 32 -383 -112 ) placeholder -144 192 0 0.2 0.2
|
||||
( 160 -256 -80 ) ( 160 -255 -80 ) ( 161 -256 -80 ) placeholder -144 192 0 0.2 0.2
|
||||
( 160 -576 -80 ) ( 161 -576 -80 ) ( 160 -576 -79 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -256 -80 ) ( 80 -256 -79 ) ( 80 -255 -80 ) placeholder -192 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 25
|
||||
{
|
||||
( -16 -480 -80 ) ( -16 -528 -80 ) ( -16 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( 32 -528 -96 ) ( 32 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -528 -112 ) ( 32 -480 -112 ) ( -16 -480 -112 ) placeholder -160 160 0 0.2 0.2
|
||||
( -16 -480 -80 ) ( 32 -480 -80 ) ( -16 -528 -80 ) placeholder -160 160 0 0.2 0.2
|
||||
( 32 -480 -112 ) ( 32 -480 -80 ) ( -16 -480 -80 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( 32 -480 -80 ) ( 32 -528 -96 ) placeholder -160 160 0 0.2 0.2
|
||||
( 32 -528 -96 ) ( 32 -480 -80 ) ( 32 -480 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 26
|
||||
{
|
||||
( 32 -528 -96 ) ( 32 -576 -80 ) ( 32 -576 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
( 32 -528 -96 ) ( 80 -528 -80 ) ( 32 -576 -80 ) placeholder -144 176 0 0.2 0.2
|
||||
( 32 -576 -80 ) ( 80 -576 -80 ) ( 80 -576 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -576 -112 ) ( 80 -528 -112 ) ( 32 -528 -112 ) placeholder -144 176 0 0.2 0.2
|
||||
( 32 -576 -80 ) ( 80 -528 -80 ) ( 80 -576 -80 ) placeholder -144 176 0 0.2 0.2
|
||||
( 80 -528 -112 ) ( 80 -528 -80 ) ( 32 -528 -96 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -576 -80 ) ( 80 -528 -80 ) ( 80 -528 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 27
|
||||
{
|
||||
( 32 -480 -80 ) ( 32 -528 -96 ) ( 32 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -480 -80 ) ( 80 -528 -80 ) ( 32 -528 -96 ) placeholder -144 160 0 0.2 0.2
|
||||
( 32 -528 -96 ) ( 80 -528 -80 ) ( 80 -528 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -528 -112 ) ( 80 -480 -112 ) ( 32 -480 -112 ) placeholder -144 160 0 0.2 0.2
|
||||
( 32 -480 -80 ) ( 80 -480 -80 ) ( 80 -528 -80 ) placeholder -144 160 0 0.2 0.2
|
||||
( 80 -480 -112 ) ( 80 -480 -80 ) ( 32 -480 -80 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -528 -80 ) ( 80 -480 -80 ) ( 80 -480 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 28
|
||||
{
|
||||
( 224 256 112 ) ( 224 257 112 ) ( 224 256 113 ) placeholder 170.66669 8 0 -0.375 0.4
|
||||
( 224 256 112 ) ( 224 256 113 ) ( 225 256 112 ) placeholder 224 -256 0 0.4 0.4
|
||||
( 224 256 112 ) ( 225 256 112 ) ( 224 257 112 ) placeholder 47.999992 138.99998 0 -0.4 0.4
|
||||
( 320 336 208 ) ( 320 337 208 ) ( 321 336 208 ) placeholder 208 112 0 0.4 0.4
|
||||
( 320 352 128 ) ( 321 352 128 ) ( 320 352 129 ) placeholder 48 -256 0 -0.4 0.4
|
||||
( 320 336 128 ) ( 320 336 129 ) ( 320 337 128 ) placeholder 128 -256 0 0.4 0.4
|
||||
}
|
||||
}
|
||||
// entity 1
|
||||
{
|
||||
"classname" "info_player_start"
|
||||
"origin" "112 -48 56"
|
||||
}
|
||||
@@ -1,303 +0,0 @@
|
||||
// Game: Generic
|
||||
// Format: Standard
|
||||
// entity 0
|
||||
{
|
||||
"classname" "worldspawn"
|
||||
"wad" ""
|
||||
// brush 0
|
||||
{
|
||||
( -112 -672 -80 ) ( -112 -720 -80 ) ( -112 -720 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
( -112 -720 -80 ) ( -64 -720 -80 ) ( -64 -720 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -720 -112 ) ( -64 -672 -112 ) ( -112 -672 -112 ) placeholder 64 224 0 0.2 0.2
|
||||
( -112 -672 -80 ) ( -64 -720 -80 ) ( -112 -720 -80 ) placeholder 64 224 0 0.2 0.2
|
||||
( -64 -672 -112 ) ( -64 -672 -96 ) ( -112 -672 -80 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -112 -672 -80 ) ( -64 -672 -96 ) ( -64 -720 -80 ) placeholder 64 224 0 0.2 0.2
|
||||
( -64 -720 -80 ) ( -64 -672 -96 ) ( -64 -672 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 1
|
||||
{
|
||||
( 0 -128 -16 ) ( 0 -127 -16 ) ( 0 -128 -15 ) placeholder 0 0 0 1 1
|
||||
( 0 -128 -16 ) ( 0 -128 -15 ) ( 1 -128 -16 ) placeholder -32 0 0 1 1
|
||||
( 0 -128 -16 ) ( 1 -128 -16 ) ( 0 -127 -16 ) placeholder -32 0 0 1 1
|
||||
( 64 -96 0 ) ( 64 -95 0 ) ( 65 -96 0 ) placeholder -32 0 0 1 1
|
||||
( 64 -96 0 ) ( 65 -96 0 ) ( 64 -96 1 ) placeholder -32 0 0 1 1
|
||||
( 64 -96 0 ) ( 64 -96 1 ) ( 64 -95 0 ) placeholder 0 0 0 1 1
|
||||
}
|
||||
// brush 2
|
||||
{
|
||||
( 64 -144 -32 ) ( 64 -143 -32 ) ( 64 -144 -31 ) placeholder -16 -16 0 1 1
|
||||
( 64 -144 -32 ) ( 64 -144 -31 ) ( 65 -144 -32 ) placeholder -32 -16 0 1 1
|
||||
( 64 -144 -32 ) ( 65 -144 -32 ) ( 64 -143 -32 ) placeholder -32 16 0 1 1
|
||||
( 192 -80 -16 ) ( 192 -79 -16 ) ( 193 -80 -16 ) placeholder -32 16 0 1 1
|
||||
( 192 -80 -16 ) ( 193 -80 -16 ) ( 192 -80 -15 ) placeholder -32 -16 0 1 1
|
||||
( 192 -80 -16 ) ( 192 -80 -15 ) ( 192 -79 -16 ) placeholder -16 -16 0 1 1
|
||||
}
|
||||
// brush 3
|
||||
{
|
||||
( 80 16 32 ) ( 80 15 32 ) ( 80 16 31 ) placeholder 0 -16 180 1 1
|
||||
( 144 -96 16 ) ( 145 -96 16 ) ( 144 -96 15 ) placeholder 16 -16 0 1 -1
|
||||
( 144 -96 16 ) ( 144 -97 16 ) ( 145 -96 16 ) placeholder 16 0 0 1 -1
|
||||
( 80 16 32 ) ( 81 16 32 ) ( 80 15 32 ) placeholder 16 0 0 1 -1
|
||||
( 80 16 32 ) ( 80 16 31 ) ( 81 16 32 ) placeholder 16 -16 0 1 -1
|
||||
( 144 -96 16 ) ( 144 -96 15 ) ( 144 -97 16 ) placeholder 0 -16 180 1 1
|
||||
}
|
||||
// brush 4
|
||||
{
|
||||
( 240 -64 -80 ) ( 240 0 -80 ) ( 240 -112 -56 ) __TB_empty -128 -60 0 1 2
|
||||
( 240 -112 -56 ) ( 304 -112 -80 ) ( 240 -64 -80 ) __TB_empty -32 8 0 1 -2
|
||||
( 240 0 -80 ) ( 288 -48 -56 ) ( 240 -112 -56 ) __TB_empty -80.41865 55.557785 323.1301 1.1743635 1.1509081
|
||||
( 288 -48 -56 ) ( 304 -112 -40 ) ( 240 -112 -56 ) __TB_empty -32 112 0 1 1
|
||||
( 240 -112 -56 ) ( 304 -112 -40 ) ( 304 -112 -80 ) __TB_empty -32 -16.941177 0 1 -2
|
||||
( 304 -112 -80 ) ( 304 0 -80 ) ( 240 0 -80 ) __TB_empty -32 144 0 1 1
|
||||
( 288 -48 -56 ) ( 320 -48 -56 ) ( 304 -112 -40 ) __TB_empty -32 112 0 1 1
|
||||
( 304 0 -80 ) ( 320 -48 -56 ) ( 288 -48 -56 ) __TB_empty -32 -32 0 1 -1
|
||||
( 304 -112 -80 ) ( 320 -48 -56 ) ( 304 0 -80 ) __TB_empty 144 -52 0 1 2
|
||||
( 304 -112 -40 ) ( 320 -48 -56 ) ( 304 -112 -80 ) __TB_empty 118.95169 -69.728745 0 1 1.1
|
||||
}
|
||||
// brush 5
|
||||
{
|
||||
( -144 -560 -80 ) ( -144 -559 -80 ) ( -144 -560 -79 ) placeholder -80 96 0 0.2 0.2
|
||||
( -144 -320 -80 ) ( -144 -320 -79 ) ( -143 -320 -80 ) placeholder -32 96 0 0.2 0.2
|
||||
( -144 -560 -80 ) ( -143 -560 -80 ) ( -144 -559 -80 ) placeholder -32 80 0 0.2 0.2
|
||||
( -16 -432 16 ) ( -16 -431 16 ) ( -15 -432 16 ) placeholder -32 80 0 0.2 0.2
|
||||
( -16 -304 -48 ) ( -15 -304 -48 ) ( -16 -304 -47 ) placeholder -32 96 0 0.2 0.2
|
||||
( 304 -432 -48 ) ( 304 -432 -47 ) ( 304 -431 -48 ) placeholder -80 96 0 0.2 0.2
|
||||
}
|
||||
// brush 6
|
||||
{
|
||||
( -160 -240 -16 ) ( -160 -241 -16 ) ( -160 -240 -17 ) placeholder -240 96 180 1 1
|
||||
( -64 -304 -32 ) ( -63 -304 -32 ) ( -64 -304 -33 ) placeholder 208 96 0 1 -1
|
||||
( -64 -352 -80 ) ( -64 -353 -80 ) ( -63 -352 -80 ) placeholder 208 240 0 1 -1
|
||||
( -160 -240 16 ) ( -159 -240 16 ) ( -160 -241 16 ) placeholder 208 240 0 1 -1
|
||||
( -160 192 -16 ) ( -160 192 -17 ) ( -159 192 -16 ) placeholder 208 96 0 1 -1
|
||||
( -144 -352 -32 ) ( -144 -352 -33 ) ( -144 -353 -32 ) placeholder -240 96 180 1 1
|
||||
}
|
||||
// brush 7
|
||||
{
|
||||
( 162.74516600406096 -112 -80 ) ( 163.4522727852475 -111.29289321881345 -80 ) ( 162.74516600406096 -112 -79 ) placeholder 190.39192 0 0 0.70710677 1
|
||||
( 162.74516600406096 -112 -80 ) ( 162.74516600406096 -112 -79 ) ( 163.45227278524752 -112.70710678118652 -80 ) placeholder -38.156433 0 0 0.70710677 1
|
||||
( 162.74516600406096 -112 -80 ) ( 163.45227278524752 -112.70710678118652 -80 ) ( 163.4522727852475 -111.29289321881345 -80 ) placeholder -2.27417 3.882248 315 1 1
|
||||
( 253.25483399593907 -112 -48 ) ( 253.96194077712562 -111.29289321881353 -48 ) ( 253.9619407771256 -112.70710678118664 -48 ) placeholder 163.08612 199.7645 315 0.3 0.3
|
||||
( 253.25483399593907 -112 -64 ) ( 253.25483399593907 -112 -63 ) ( 253.96194077712562 -111.29289321881353 -64 ) placeholder -6.156433 0 0 0.70710677 1
|
||||
( 253.25483399593907 -112 -64 ) ( 253.9619407771256 -112.70710678118664 -64 ) ( 253.25483399593907 -112 -63 ) placeholder 97.60808 0 180 0.70710677 -1
|
||||
}
|
||||
// brush 8
|
||||
{
|
||||
( -32 96 -80 ) ( -32 97 -80 ) ( -32 96 -79 ) placeholder 0 0 0 1 1
|
||||
( 96 272 -64 ) ( 97 272.625 -64 ) ( 96 272 -63 ) placeholder 0 0 0 1 1
|
||||
( -32 96 -80 ) ( -31 96.625 -80 ) ( -32 97 -80 ) placeholder -73.70788 15.668304 32.00539 1.1792476 0.87100387
|
||||
( 96 272 -32 ) ( 96 273 -32 ) ( 97 272.625 -32 ) placeholder -73.70788 15.668304 32.00539 1.1792476 0.87100387
|
||||
( -32 96 -80 ) ( -32 96 -79 ) ( -31 96.625 -80 ) placeholder 0 0 0 1 1
|
||||
( 96 272 -64 ) ( 96 272 -63 ) ( 96 273 -64 ) placeholder -80 0 0 1 1
|
||||
}
|
||||
// brush 9
|
||||
{
|
||||
( -64 -672 -96 ) ( -64 -720 -80 ) ( -64 -720 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
( -64 -672 -96 ) ( -16 -672 -80 ) ( -64 -720 -80 ) placeholder 80 224 0 0.2 0.2
|
||||
( -64 -720 -80 ) ( -16 -720 -80 ) ( -16 -720 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -720 -112 ) ( -16 -672 -112 ) ( -64 -672 -112 ) placeholder 80 224 0 0.2 0.2
|
||||
( -64 -720 -80 ) ( -16 -672 -80 ) ( -16 -720 -80 ) placeholder 80 224 0 0.2 0.2
|
||||
( -16 -672 -112 ) ( -16 -672 -80 ) ( -64 -672 -96 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -720 -80 ) ( -16 -672 -80 ) ( -16 -672 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 10
|
||||
{
|
||||
( -16 -672 -80 ) ( -16 -720 -80 ) ( -16 -720 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
( -16 -672 -80 ) ( 32 -672 -48 ) ( -16 -720 -80 ) placeholder -160 224 0 0.2 0.2
|
||||
( -16 -720 -80 ) ( 32 -720 -80 ) ( 32 -720 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -720 -80 ) ( 32 -672 -48 ) ( 32 -720 -80 ) placeholder -160 224 0 0.2 0.2
|
||||
( 32 -720 -112 ) ( 32 -672 -112 ) ( -16 -672 -112 ) placeholder -160 224 0 0.2 0.2
|
||||
( 32 -672 -112 ) ( 32 -672 -48 ) ( -16 -672 -80 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -720 -80 ) ( 32 -672 -48 ) ( 32 -672 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 11
|
||||
{
|
||||
( 32 -672 -48 ) ( 32 -720 -80 ) ( 32 -720 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
( 32 -720 -80 ) ( 80 -720 -80 ) ( 80 -720 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 80 -720 -80 ) ( 32 -720 -80 ) placeholder -144 224 0 0.2 0.2
|
||||
( 80 -720 -112 ) ( 80 -672 -112 ) ( 32 -672 -112 ) placeholder -144 224 0 0.2 0.2
|
||||
( 80 -672 -112 ) ( 80 -672 -80 ) ( 32 -672 -48 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 80 -672 -80 ) ( 80 -720 -80 ) placeholder -144 224 0 0.2 0.2
|
||||
( 80 -720 -80 ) ( 80 -672 -80 ) ( 80 -672 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 12
|
||||
{
|
||||
( -112 -624 -80 ) ( -112 -672 -80 ) ( -112 -672 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
( -112 -672 -80 ) ( -64 -672 -96 ) ( -64 -672 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -672 -112 ) ( -64 -624 -112 ) ( -112 -624 -112 ) placeholder 64 208 0 0.2 0.2
|
||||
( -112 -624 -80 ) ( -64 -624 -80 ) ( -112 -672 -80 ) placeholder 64 208 0 0.2 0.2
|
||||
( -64 -624 -112 ) ( -64 -624 -80 ) ( -112 -624 -80 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -112 -672 -80 ) ( -64 -624 -80 ) ( -64 -672 -96 ) placeholder 64 208 0 0.2 0.2
|
||||
( -64 -672 -96 ) ( -64 -624 -80 ) ( -64 -624 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 13
|
||||
{
|
||||
( -112 -384 -112 ) ( -112 -383 -112 ) ( -112 -384 -111 ) placeholder -192 -64 0 0.2 0.2
|
||||
( -112 -624 -112 ) ( -112 -624 -111 ) ( -111 -624 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -112 -384 -112 ) ( -111 -384 -112 ) ( -112 -383 -112 ) placeholder 64 192 0 0.2 0.2
|
||||
( 16 -256 -80 ) ( 16 -255 -80 ) ( 17 -256 -80 ) placeholder 64 192 0 0.2 0.2
|
||||
( 16 -576 -80 ) ( 17 -576 -80 ) ( 16 -576 -79 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -256 -80 ) ( -64 -256 -79 ) ( -64 -255 -80 ) placeholder -192 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 14
|
||||
{
|
||||
( -64 -624 -80 ) ( -64 -672 -96 ) ( -64 -672 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
( -64 -624 -80 ) ( -16 -672 -80 ) ( -64 -672 -96 ) placeholder 80 208 0 0.2 0.2
|
||||
( -64 -672 -96 ) ( -16 -672 -80 ) ( -16 -672 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -672 -112 ) ( -16 -624 -112 ) ( -64 -624 -112 ) placeholder 80 208 0 0.2 0.2
|
||||
( -64 -624 -80 ) ( -16 -624 -80 ) ( -16 -672 -80 ) placeholder 80 208 0 0.2 0.2
|
||||
( -16 -624 -112 ) ( -16 -624 -80 ) ( -64 -624 -80 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -672 -80 ) ( -16 -624 -80 ) ( -16 -624 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 15
|
||||
{
|
||||
( -112 -528 -80 ) ( -112 -576 -80 ) ( -112 -576 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
( -112 -528 -80 ) ( -64 -528 -48 ) ( -112 -576 -80 ) placeholder 64 176 0 0.2 0.2
|
||||
( -112 -576 -80 ) ( -64 -576 -80 ) ( -64 -576 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -112 -576 -80 ) ( -64 -528 -48 ) ( -64 -576 -80 ) placeholder 64 176 0 0.2 0.2
|
||||
( -64 -576 -112 ) ( -64 -528 -112 ) ( -112 -528 -112 ) placeholder 64 176 0 0.2 0.2
|
||||
( -64 -528 -112 ) ( -64 -528 -48 ) ( -112 -528 -80 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -576 -80 ) ( -64 -528 -48 ) ( -64 -528 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 16
|
||||
{
|
||||
( -64 -384 -112 ) ( -64 -383 -112 ) ( -64 -384 -111 ) placeholder -192 -64 0 0.2 0.2
|
||||
( -64 -624 -112 ) ( -64 -624 -111 ) ( -63 -624 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -64 -384 -112 ) ( -63 -384 -112 ) ( -64 -383 -112 ) placeholder 80 192 0 0.2 0.2
|
||||
( 64 -256 -80 ) ( 64 -255 -80 ) ( 65 -256 -80 ) placeholder 80 192 0 0.2 0.2
|
||||
( 64 -576 -80 ) ( 65 -576 -80 ) ( 64 -576 -79 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -256 -80 ) ( -16 -256 -79 ) ( -16 -255 -80 ) placeholder -192 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 17
|
||||
{
|
||||
( -16 -624 -80 ) ( -16 -672 -80 ) ( -16 -672 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
( -16 -624 -80 ) ( 32 -672 -48 ) ( -16 -672 -80 ) placeholder -160 208 0 0.2 0.2
|
||||
( -16 -672 -80 ) ( 32 -672 -48 ) ( 32 -672 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -672 -112 ) ( 32 -624 -112 ) ( -16 -624 -112 ) placeholder -160 208 0 0.2 0.2
|
||||
( -16 -624 -80 ) ( 32 -624 -80 ) ( 32 -672 -48 ) placeholder -160 208 0 0.2 0.2
|
||||
( 32 -624 -112 ) ( 32 -624 -80 ) ( -16 -624 -80 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 32 -624 -80 ) ( 32 -624 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 18
|
||||
{
|
||||
( -112 -480 -80 ) ( -112 -528 -80 ) ( -112 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -112 -480 -80 ) ( -64 -528 -48 ) ( -112 -528 -80 ) placeholder 64 160 0 0.2 0.2
|
||||
( -112 -528 -80 ) ( -64 -528 -48 ) ( -64 -528 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -528 -112 ) ( -64 -480 -112 ) ( -112 -480 -112 ) placeholder 64 160 0 0.2 0.2
|
||||
( -112 -480 -80 ) ( -64 -480 -80 ) ( -64 -528 -48 ) placeholder 64 160 0 0.2 0.2
|
||||
( -64 -480 -112 ) ( -64 -480 -80 ) ( -112 -480 -80 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -64 -480 -80 ) ( -64 -480 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 19
|
||||
{
|
||||
( -64 -528 -48 ) ( -64 -576 -80 ) ( -64 -576 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
( -64 -576 -80 ) ( -16 -576 -80 ) ( -16 -576 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -16 -576 -80 ) ( -64 -576 -80 ) placeholder 80 176 0 0.2 0.2
|
||||
( -16 -576 -112 ) ( -16 -528 -112 ) ( -64 -528 -112 ) placeholder 80 176 0 0.2 0.2
|
||||
( -16 -528 -112 ) ( -16 -528 -80 ) ( -64 -528 -48 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -16 -528 -80 ) ( -16 -576 -80 ) placeholder 80 176 0 0.2 0.2
|
||||
( -16 -576 -80 ) ( -16 -528 -80 ) ( -16 -528 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 20
|
||||
{
|
||||
( -16 -384 -112 ) ( -16 -383 -112 ) ( -16 -384 -111 ) placeholder -192 -64 0 0.2 0.2
|
||||
( -16 -624 -112 ) ( -16 -624 -111 ) ( -15 -624 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -384 -112 ) ( -15 -384 -112 ) ( -16 -383 -112 ) placeholder -160 192 0 0.2 0.2
|
||||
( 112 -256 -80 ) ( 112 -255 -80 ) ( 113 -256 -80 ) placeholder -160 192 0 0.2 0.2
|
||||
( 112 -576 -80 ) ( 113 -576 -80 ) ( 112 -576 -79 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -256 -80 ) ( 32 -256 -79 ) ( 32 -255 -80 ) placeholder -192 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 21
|
||||
{
|
||||
( 32 -624 -80 ) ( 32 -672 -48 ) ( 32 -672 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 80 -672 -80 ) ( 80 -672 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -672 -112 ) ( 80 -624 -112 ) ( 32 -624 -112 ) placeholder -144 208 0 0.2 0.2
|
||||
( 32 -624 -80 ) ( 80 -624 -80 ) ( 32 -672 -48 ) placeholder -144 208 0 0.2 0.2
|
||||
( 80 -624 -112 ) ( 80 -624 -80 ) ( 32 -624 -80 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 80 -624 -80 ) ( 80 -672 -80 ) placeholder -144 208 0 0.2 0.2
|
||||
( 80 -672 -80 ) ( 80 -624 -80 ) ( 80 -624 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 22
|
||||
{
|
||||
( -64 -480 -80 ) ( -64 -528 -48 ) ( -64 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -16 -528 -80 ) ( -16 -528 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -528 -112 ) ( -16 -480 -112 ) ( -64 -480 -112 ) placeholder 80 160 0 0.2 0.2
|
||||
( -64 -480 -80 ) ( -16 -480 -80 ) ( -64 -528 -48 ) placeholder 80 160 0 0.2 0.2
|
||||
( -16 -480 -112 ) ( -16 -480 -80 ) ( -64 -480 -80 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -16 -480 -80 ) ( -16 -528 -80 ) placeholder 80 160 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( -16 -480 -80 ) ( -16 -480 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 23
|
||||
{
|
||||
( -16 -528 -80 ) ( -16 -576 -80 ) ( -16 -576 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
( -16 -576 -80 ) ( 32 -576 -80 ) ( 32 -576 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -576 -112 ) ( 32 -528 -112 ) ( -16 -528 -112 ) placeholder -160 176 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( 32 -576 -80 ) ( -16 -576 -80 ) placeholder -160 176 0 0.2 0.2
|
||||
( 32 -528 -112 ) ( 32 -528 -96 ) ( -16 -528 -80 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( 32 -528 -96 ) ( 32 -576 -80 ) placeholder -160 176 0 0.2 0.2
|
||||
( 32 -576 -80 ) ( 32 -528 -96 ) ( 32 -528 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 24
|
||||
{
|
||||
( 32 -384 -112 ) ( 32 -383 -112 ) ( 32 -384 -111 ) placeholder -192 -64 0 0.2 0.2
|
||||
( 32 -624 -112 ) ( 32 -624 -111 ) ( 33 -624 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 32 -384 -112 ) ( 33 -384 -112 ) ( 32 -383 -112 ) placeholder -144 192 0 0.2 0.2
|
||||
( 160 -256 -80 ) ( 160 -255 -80 ) ( 161 -256 -80 ) placeholder -144 192 0 0.2 0.2
|
||||
( 160 -576 -80 ) ( 161 -576 -80 ) ( 160 -576 -79 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -256 -80 ) ( 80 -256 -79 ) ( 80 -255 -80 ) placeholder -192 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 25
|
||||
{
|
||||
( -16 -480 -80 ) ( -16 -528 -80 ) ( -16 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( 32 -528 -96 ) ( 32 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -528 -112 ) ( 32 -480 -112 ) ( -16 -480 -112 ) placeholder -160 160 0 0.2 0.2
|
||||
( -16 -480 -80 ) ( 32 -480 -80 ) ( -16 -528 -80 ) placeholder -160 160 0 0.2 0.2
|
||||
( 32 -480 -112 ) ( 32 -480 -80 ) ( -16 -480 -80 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( 32 -480 -80 ) ( 32 -528 -96 ) placeholder -160 160 0 0.2 0.2
|
||||
( 32 -528 -96 ) ( 32 -480 -80 ) ( 32 -480 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 26
|
||||
{
|
||||
( 32 -528 -96 ) ( 32 -576 -80 ) ( 32 -576 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
( 32 -528 -96 ) ( 80 -528 -80 ) ( 32 -576 -80 ) placeholder -144 176 0 0.2 0.2
|
||||
( 32 -576 -80 ) ( 80 -576 -80 ) ( 80 -576 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -576 -112 ) ( 80 -528 -112 ) ( 32 -528 -112 ) placeholder -144 176 0 0.2 0.2
|
||||
( 32 -576 -80 ) ( 80 -528 -80 ) ( 80 -576 -80 ) placeholder -144 176 0 0.2 0.2
|
||||
( 80 -528 -112 ) ( 80 -528 -80 ) ( 32 -528 -96 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -576 -80 ) ( 80 -528 -80 ) ( 80 -528 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 27
|
||||
{
|
||||
( 32 -480 -80 ) ( 32 -528 -96 ) ( 32 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -480 -80 ) ( 80 -528 -80 ) ( 32 -528 -96 ) placeholder -144 160 0 0.2 0.2
|
||||
( 32 -528 -96 ) ( 80 -528 -80 ) ( 80 -528 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -528 -112 ) ( 80 -480 -112 ) ( 32 -480 -112 ) placeholder -144 160 0 0.2 0.2
|
||||
( 32 -480 -80 ) ( 80 -480 -80 ) ( 80 -528 -80 ) placeholder -144 160 0 0.2 0.2
|
||||
( 80 -480 -112 ) ( 80 -480 -80 ) ( 32 -480 -80 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -528 -80 ) ( 80 -480 -80 ) ( 80 -480 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 28
|
||||
{
|
||||
( 224 256 112 ) ( 224 257 112 ) ( 224 256 113 ) placeholder 170.66669 8 0 -0.375 0.4
|
||||
( 224 256 112 ) ( 224 256 113 ) ( 225 256 112 ) placeholder 224 -256 0 0.4 0.4
|
||||
( 224 256 112 ) ( 225 256 112 ) ( 224 257 112 ) placeholder 47.999992 138.99998 0 -0.4 0.4
|
||||
( 320 336 208 ) ( 320 337 208 ) ( 321 336 208 ) placeholder 208 112 0 0.4 0.4
|
||||
( 320 352 128 ) ( 321 352 128 ) ( 320 352 129 ) placeholder 48 -256 0 -0.4 0.4
|
||||
( 320 336 128 ) ( 320 336 129 ) ( 320 337 128 ) placeholder 128 -256 0 0.4 0.4
|
||||
}
|
||||
// brush 29
|
||||
{
|
||||
( 0 -144 -32 ) ( 16 -304 -64 ) ( 16 -144 -64 ) placeholder 0 0 0 1 1
|
||||
( 32 -144 -16 ) ( 0 -304 -32 ) ( 0 -144 -32 ) placeholder 0 0 0 1 1
|
||||
( 64 -304 -32 ) ( 0 -304 -32 ) ( 32 -304 -16 ) placeholder 0 0 0 1 1
|
||||
( 64 -144 -64 ) ( 16 -304 -64 ) ( 64 -304 -64 ) placeholder 0 0 0 1 1
|
||||
( 64 -144 -32 ) ( 16 -144 -64 ) ( 64 -144 -64 ) placeholder 0 0 0 1 1
|
||||
( 64 -144 -32 ) ( 32 -304 -16 ) ( 32 -144 -16 ) placeholder 0 0 0 1 1
|
||||
( 64 -144 -32 ) ( 64 -304 -64 ) ( 64 -304 -32 ) placeholder 0 0 0 1 1
|
||||
}
|
||||
}
|
||||
// entity 1
|
||||
{
|
||||
"classname" "info_player_start"
|
||||
"origin" "112 -48 56"
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# Blender 5.0.0 MTL File: 'None'
|
||||
# www.blender.org
|
||||
@@ -6,288 +6,43 @@
|
||||
"wad" ""
|
||||
// brush 0
|
||||
{
|
||||
( -112 -672 -80 ) ( -112 -720 -80 ) ( -112 -720 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
( -112 -720 -80 ) ( -64 -720 -80 ) ( -64 -720 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -720 -112 ) ( -64 -672 -112 ) ( -112 -672 -112 ) placeholder 64 224 0 0.2 0.2
|
||||
( -112 -672 -80 ) ( -64 -720 -80 ) ( -112 -720 -80 ) placeholder 64 224 0 0.2 0.2
|
||||
( -64 -672 -112 ) ( -64 -672 -96 ) ( -112 -672 -80 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -112 -672 -80 ) ( -64 -672 -96 ) ( -64 -720 -80 ) placeholder 64 224 0 0.2 0.2
|
||||
( -64 -720 -80 ) ( -64 -672 -96 ) ( -64 -672 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
( -80 -16 -32 ) ( -80 -15 -32 ) ( -80 -16 -31 ) 256_arcade_carpet 96 -16 0 0.2 0.2
|
||||
( -112 -16 -32 ) ( -112 -16 -31 ) ( -111 -16 -32 ) 256_arcade_carpet 80 -16 0 0.2 0.2
|
||||
( -112 -16 -32 ) ( -111 -16 -32 ) ( -112 -15 -32 ) 256_arcade_carpet 80 -96 0 0.2 0.2
|
||||
( 48 96 -16 ) ( 48 97 -16 ) ( 49 96 -16 ) 256_arcade_carpet 5.3333435 -57.379303 0 0.2 0.2
|
||||
( 48 112 -16 ) ( 49 112 -16 ) ( 48 112 -15 ) 256_arcade_carpet 80 -16 0 0.2 0.2
|
||||
( 80 96 -16 ) ( 80 96 -15 ) ( 80 97 -16 ) 256_arcade_carpet 96 -16 0 0.2 0.2
|
||||
}
|
||||
// brush 1
|
||||
{
|
||||
( 0 -128 -16 ) ( 0 -127 -16 ) ( 0 -128 -15 ) placeholder 0 0 0 1 1
|
||||
( 0 -128 -16 ) ( 0 -128 -15 ) ( 1 -128 -16 ) placeholder -32 0 0 1 1
|
||||
( 0 -128 -16 ) ( 1 -128 -16 ) ( 0 -127 -16 ) placeholder -32 0 0 1 1
|
||||
( 64 -96 0 ) ( 64 -95 0 ) ( 65 -96 0 ) placeholder -32 0 0 1 1
|
||||
( 64 -96 0 ) ( 65 -96 0 ) ( 64 -96 1 ) placeholder -32 0 0 1 1
|
||||
( 64 -96 0 ) ( 64 -96 1 ) ( 64 -95 0 ) placeholder 0 0 0 1 1
|
||||
( -48 0 4 ) ( -48 1 4 ) ( -48 0 5 ) 256_cement_dirty_rough_wall 0 0 0 1 1
|
||||
( 48 -16 -16 ) ( 47 -16 -16 ) ( 48 -16 -15 ) 256_cement_dirty_rough_wall 0 0 0 1 1
|
||||
( 48 -16 -16 ) ( 48 -15 -16 ) ( 47 -16 -16 ) 256_cement_dirty_rough_wall 0 0 0 1 1
|
||||
( -48 0 80 ) ( -49 0 80 ) ( -48 1 80 ) 256_cement_dirty_rough_wall 0 0 0 1 1
|
||||
( -48 0 4 ) ( -48 0 5 ) ( -49 0 4 ) 256_cement_dirty_rough_wall 0 0 0 1 1
|
||||
( 48 -16 -16 ) ( 48 -16 -15 ) ( 48 -15 -16 ) 256_cement_dirty_rough_wall 0 0 0 1 1
|
||||
}
|
||||
// brush 2
|
||||
{
|
||||
( 64 -144 -32 ) ( 64 -143 -32 ) ( 64 -144 -31 ) placeholder -16 -16 0 1 1
|
||||
( 64 -144 -32 ) ( 64 -144 -31 ) ( 65 -144 -32 ) placeholder -32 -16 0 1 1
|
||||
( 64 -144 -32 ) ( 65 -144 -32 ) ( 64 -143 -32 ) placeholder -32 16 0 1 1
|
||||
( 192 -80 -16 ) ( 192 -79 -16 ) ( 193 -80 -16 ) placeholder -32 16 0 1 1
|
||||
( 192 -80 -16 ) ( 193 -80 -16 ) ( 192 -80 -15 ) placeholder -32 -16 0 1 1
|
||||
( 192 -80 -16 ) ( 192 -80 -15 ) ( 192 -79 -16 ) placeholder -16 -16 0 1 1
|
||||
( -91.71281292110203 32 4 ) ( -91.21281292110203 32.86602540378445 4 ) ( -91.71281292110203 32 5 ) 256_cement_dirty_rough_wall 0 0 0 1 1
|
||||
( -30.430780618346965 -21.856406460551035 -16 ) ( -31.296806022131403 -21.356406460551035 -16 ) ( -30.430780618346965 -21.856406460551035 -15 ) 256_cement_dirty_rough_wall 0 0 0 1 1
|
||||
( -30.430780618346965 -21.856406460551035 -16 ) ( -29.930780618346965 -20.990381056766598 -16 ) ( -31.296806022131403 -21.356406460551035 -16 ) 256_cement_dirty_rough_wall 0 0 0 1 1
|
||||
( -105.56921938165303 40 80 ) ( -106.43524478543748 40.500000000000014 80 ) ( -105.06921938165303 40.86602540378446 80 ) 256_cement_dirty_rough_wall 0 0 0 1 1
|
||||
( -105.56921938165303 40 4 ) ( -105.56921938165303 40 5 ) ( -106.43524478543748 40.500000000000014 4 ) 256_cement_dirty_rough_wall 0 0 0 1 1
|
||||
( -44.28718707889798 -13.856406460551028 -16 ) ( -44.28718707889798 -13.856406460551028 -15 ) ( -43.78718707889798 -12.99038105676659 -16 ) 256_cement_dirty_rough_wall 0 0 0 1 1
|
||||
}
|
||||
// brush 3
|
||||
{
|
||||
( 80 16 32 ) ( 80 15 32 ) ( 80 16 31 ) placeholder 0 -16 180 1 1
|
||||
( 144 -96 16 ) ( 145 -96 16 ) ( 144 -96 15 ) placeholder 16 -16 0 1 -1
|
||||
( 144 -96 16 ) ( 144 -97 16 ) ( 145 -96 16 ) placeholder 16 0 0 1 -1
|
||||
( 80 16 32 ) ( 81 16 32 ) ( 80 15 32 ) placeholder 16 0 0 1 -1
|
||||
( 80 16 32 ) ( 80 16 31 ) ( 81 16 32 ) placeholder 16 -16 0 1 -1
|
||||
( 144 -96 16 ) ( 144 -96 15 ) ( 144 -97 16 ) placeholder 0 -16 180 1 1
|
||||
}
|
||||
// brush 4
|
||||
{
|
||||
( 224 -48 -80 ) ( 224 16 -80 ) ( 224 -96 -56 ) __TB_empty -128 -60 0 1 2
|
||||
( 224 -96 -56 ) ( 288 -96 -80 ) ( 224 -48 -80 ) __TB_empty -32 8 0 1 -2
|
||||
( 224 16 -80 ) ( 272 -32 -56 ) ( 224 -96 -56 ) __TB_empty -134.26993 -44.949593 0 1 2
|
||||
( 272 -32 -56 ) ( 288 -96 -40 ) ( 224 -96 -56 ) __TB_empty -32 112 0 1 1
|
||||
( 224 -96 -56 ) ( 288 -96 -40 ) ( 288 -96 -80 ) __TB_empty -32 -16.941177 0 1 -2
|
||||
( 288 -96 -80 ) ( 288 16 -80 ) ( 224 16 -80 ) __TB_empty -32 144 0 1 1
|
||||
( 272 -32 -56 ) ( 304 -32 -56 ) ( 288 -96 -40 ) __TB_empty -32 112 0 1 1
|
||||
( 288 16 -80 ) ( 304 -32 -56 ) ( 272 -32 -56 ) __TB_empty -32 -24 0 1 -2
|
||||
( 288 -96 -80 ) ( 304 -32 -56 ) ( 288 16 -80 ) __TB_empty 144 -52 0 1 2
|
||||
( 288 -96 -40 ) ( 304 -32 -56 ) ( 288 -96 -80 ) __TB_empty 118.95169 -69.728745 0 1 2
|
||||
}
|
||||
// brush 5
|
||||
{
|
||||
( -144 -560 -80 ) ( -144 -559 -80 ) ( -144 -560 -79 ) placeholder -80 96 0 0.2 0.2
|
||||
( -144 -320 -80 ) ( -144 -320 -79 ) ( -143 -320 -80 ) placeholder -32 96 0 0.2 0.2
|
||||
( -144 -560 -80 ) ( -143 -560 -80 ) ( -144 -559 -80 ) placeholder -32 80 0 0.2 0.2
|
||||
( -16 -432 16 ) ( -16 -431 16 ) ( -15 -432 16 ) placeholder -32 80 0 0.2 0.2
|
||||
( -16 -304 -48 ) ( -15 -304 -48 ) ( -16 -304 -47 ) placeholder -32 96 0 0.2 0.2
|
||||
( 304 -432 -48 ) ( 304 -432 -47 ) ( 304 -431 -48 ) placeholder -80 96 0 0.2 0.2
|
||||
}
|
||||
// brush 6
|
||||
{
|
||||
( -160 -240 -16 ) ( -160 -241 -16 ) ( -160 -240 -17 ) placeholder -240 96 180 1 1
|
||||
( -64 -304 -32 ) ( -63 -304 -32 ) ( -64 -304 -33 ) placeholder 208 96 0 1 -1
|
||||
( -64 -352 -80 ) ( -64 -353 -80 ) ( -63 -352 -80 ) placeholder 208 240 0 1 -1
|
||||
( -160 -240 16 ) ( -159 -240 16 ) ( -160 -241 16 ) placeholder 208 240 0 1 -1
|
||||
( -160 192 -16 ) ( -160 192 -17 ) ( -159 192 -16 ) placeholder 208 96 0 1 -1
|
||||
( -144 -352 -32 ) ( -144 -352 -33 ) ( -144 -353 -32 ) placeholder -240 96 180 1 1
|
||||
}
|
||||
// brush 7
|
||||
{
|
||||
( 162.74516600406096 -112 -80 ) ( 163.4522727852475 -111.29289321881345 -80 ) ( 162.74516600406096 -112 -79 ) placeholder 190.39192 0 0 0.70710677 1
|
||||
( 162.74516600406096 -112 -80 ) ( 162.74516600406096 -112 -79 ) ( 163.45227278524752 -112.70710678118652 -80 ) placeholder -38.156433 0 0 0.70710677 1
|
||||
( 162.74516600406096 -112 -80 ) ( 163.45227278524752 -112.70710678118652 -80 ) ( 163.4522727852475 -111.29289321881345 -80 ) placeholder -2.27417 3.882248 315 1 1
|
||||
( 253.25483399593907 -112 -48 ) ( 253.96194077712562 -111.29289321881353 -48 ) ( 253.9619407771256 -112.70710678118664 -48 ) placeholder -2.27417 3.882248 315 1 1
|
||||
( 253.25483399593907 -112 -64 ) ( 253.25483399593907 -112 -63 ) ( 253.96194077712562 -111.29289321881353 -64 ) placeholder -6.156433 0 0 0.70710677 1
|
||||
( 253.25483399593907 -112 -64 ) ( 253.9619407771256 -112.70710678118664 -64 ) ( 253.25483399593907 -112 -63 ) placeholder 97.60808 0 180 0.70710677 -1
|
||||
}
|
||||
// brush 8
|
||||
{
|
||||
( -32 96 -80 ) ( -32 97 -80 ) ( -32 96 -79 ) placeholder 0 0 0 1 1
|
||||
( 96 272 -64 ) ( 97 272.625 -64 ) ( 96 272 -63 ) placeholder 0 0 0 1 1
|
||||
( -32 96 -80 ) ( -31 96.625 -80 ) ( -32 97 -80 ) placeholder -73.70788 15.668304 32.00539 1.1792476 0.87100387
|
||||
( 96 272 -32 ) ( 96 273 -32 ) ( 97 272.625 -32 ) placeholder -73.70788 15.668304 32.00539 1.1792476 0.87100387
|
||||
( -32 96 -80 ) ( -32 96 -79 ) ( -31 96.625 -80 ) placeholder 0 0 0 1 1
|
||||
( 96 272 -64 ) ( 96 272 -63 ) ( 96 273 -64 ) placeholder -80 0 0 1 1
|
||||
}
|
||||
// brush 9
|
||||
{
|
||||
( -64 -672 -96 ) ( -64 -720 -80 ) ( -64 -720 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
( -64 -672 -96 ) ( -16 -672 -80 ) ( -64 -720 -80 ) placeholder 80 224 0 0.2 0.2
|
||||
( -64 -720 -80 ) ( -16 -720 -80 ) ( -16 -720 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -720 -112 ) ( -16 -672 -112 ) ( -64 -672 -112 ) placeholder 80 224 0 0.2 0.2
|
||||
( -64 -720 -80 ) ( -16 -672 -80 ) ( -16 -720 -80 ) placeholder 80 224 0 0.2 0.2
|
||||
( -16 -672 -112 ) ( -16 -672 -80 ) ( -64 -672 -96 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -720 -80 ) ( -16 -672 -80 ) ( -16 -672 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 10
|
||||
{
|
||||
( -16 -672 -80 ) ( -16 -720 -80 ) ( -16 -720 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
( -16 -672 -80 ) ( 32 -672 -48 ) ( -16 -720 -80 ) placeholder -160 224 0 0.2 0.2
|
||||
( -16 -720 -80 ) ( 32 -720 -80 ) ( 32 -720 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -720 -80 ) ( 32 -672 -48 ) ( 32 -720 -80 ) placeholder -160 224 0 0.2 0.2
|
||||
( 32 -720 -112 ) ( 32 -672 -112 ) ( -16 -672 -112 ) placeholder -160 224 0 0.2 0.2
|
||||
( 32 -672 -112 ) ( 32 -672 -48 ) ( -16 -672 -80 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -720 -80 ) ( 32 -672 -48 ) ( 32 -672 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 11
|
||||
{
|
||||
( 32 -672 -48 ) ( 32 -720 -80 ) ( 32 -720 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
( 32 -720 -80 ) ( 80 -720 -80 ) ( 80 -720 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 80 -720 -80 ) ( 32 -720 -80 ) placeholder -144 224 0 0.2 0.2
|
||||
( 80 -720 -112 ) ( 80 -672 -112 ) ( 32 -672 -112 ) placeholder -144 224 0 0.2 0.2
|
||||
( 80 -672 -112 ) ( 80 -672 -80 ) ( 32 -672 -48 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 80 -672 -80 ) ( 80 -720 -80 ) placeholder -144 224 0 0.2 0.2
|
||||
( 80 -720 -80 ) ( 80 -672 -80 ) ( 80 -672 -112 ) placeholder -224 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 12
|
||||
{
|
||||
( -112 -624 -80 ) ( -112 -672 -80 ) ( -112 -672 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
( -112 -672 -80 ) ( -64 -672 -96 ) ( -64 -672 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -672 -112 ) ( -64 -624 -112 ) ( -112 -624 -112 ) placeholder 64 208 0 0.2 0.2
|
||||
( -112 -624 -80 ) ( -64 -624 -80 ) ( -112 -672 -80 ) placeholder 64 208 0 0.2 0.2
|
||||
( -64 -624 -112 ) ( -64 -624 -80 ) ( -112 -624 -80 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -112 -672 -80 ) ( -64 -624 -80 ) ( -64 -672 -96 ) placeholder 64 208 0 0.2 0.2
|
||||
( -64 -672 -96 ) ( -64 -624 -80 ) ( -64 -624 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 13
|
||||
{
|
||||
( -112 -384 -112 ) ( -112 -383 -112 ) ( -112 -384 -111 ) placeholder -192 -64 0 0.2 0.2
|
||||
( -112 -624 -112 ) ( -112 -624 -111 ) ( -111 -624 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -112 -384 -112 ) ( -111 -384 -112 ) ( -112 -383 -112 ) placeholder 64 192 0 0.2 0.2
|
||||
( 16 -256 -80 ) ( 16 -255 -80 ) ( 17 -256 -80 ) placeholder 64 192 0 0.2 0.2
|
||||
( 16 -576 -80 ) ( 17 -576 -80 ) ( 16 -576 -79 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -256 -80 ) ( -64 -256 -79 ) ( -64 -255 -80 ) placeholder -192 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 14
|
||||
{
|
||||
( -64 -624 -80 ) ( -64 -672 -96 ) ( -64 -672 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
( -64 -624 -80 ) ( -16 -672 -80 ) ( -64 -672 -96 ) placeholder 80 208 0 0.2 0.2
|
||||
( -64 -672 -96 ) ( -16 -672 -80 ) ( -16 -672 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -672 -112 ) ( -16 -624 -112 ) ( -64 -624 -112 ) placeholder 80 208 0 0.2 0.2
|
||||
( -64 -624 -80 ) ( -16 -624 -80 ) ( -16 -672 -80 ) placeholder 80 208 0 0.2 0.2
|
||||
( -16 -624 -112 ) ( -16 -624 -80 ) ( -64 -624 -80 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -672 -80 ) ( -16 -624 -80 ) ( -16 -624 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 15
|
||||
{
|
||||
( -112 -528 -80 ) ( -112 -576 -80 ) ( -112 -576 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
( -112 -528 -80 ) ( -64 -528 -48 ) ( -112 -576 -80 ) placeholder 64 176 0 0.2 0.2
|
||||
( -112 -576 -80 ) ( -64 -576 -80 ) ( -64 -576 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -112 -576 -80 ) ( -64 -528 -48 ) ( -64 -576 -80 ) placeholder 64 176 0 0.2 0.2
|
||||
( -64 -576 -112 ) ( -64 -528 -112 ) ( -112 -528 -112 ) placeholder 64 176 0 0.2 0.2
|
||||
( -64 -528 -112 ) ( -64 -528 -48 ) ( -112 -528 -80 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -576 -80 ) ( -64 -528 -48 ) ( -64 -528 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 16
|
||||
{
|
||||
( -64 -384 -112 ) ( -64 -383 -112 ) ( -64 -384 -111 ) placeholder -192 -64 0 0.2 0.2
|
||||
( -64 -624 -112 ) ( -64 -624 -111 ) ( -63 -624 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -64 -384 -112 ) ( -63 -384 -112 ) ( -64 -383 -112 ) placeholder 80 192 0 0.2 0.2
|
||||
( 64 -256 -80 ) ( 64 -255 -80 ) ( 65 -256 -80 ) placeholder 80 192 0 0.2 0.2
|
||||
( 64 -576 -80 ) ( 65 -576 -80 ) ( 64 -576 -79 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -256 -80 ) ( -16 -256 -79 ) ( -16 -255 -80 ) placeholder -192 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 17
|
||||
{
|
||||
( -16 -624 -80 ) ( -16 -672 -80 ) ( -16 -672 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
( -16 -624 -80 ) ( 32 -672 -48 ) ( -16 -672 -80 ) placeholder -160 208 0 0.2 0.2
|
||||
( -16 -672 -80 ) ( 32 -672 -48 ) ( 32 -672 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -672 -112 ) ( 32 -624 -112 ) ( -16 -624 -112 ) placeholder -160 208 0 0.2 0.2
|
||||
( -16 -624 -80 ) ( 32 -624 -80 ) ( 32 -672 -48 ) placeholder -160 208 0 0.2 0.2
|
||||
( 32 -624 -112 ) ( 32 -624 -80 ) ( -16 -624 -80 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 32 -624 -80 ) ( 32 -624 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 18
|
||||
{
|
||||
( -112 -480 -80 ) ( -112 -528 -80 ) ( -112 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -112 -480 -80 ) ( -64 -528 -48 ) ( -112 -528 -80 ) placeholder 64 160 0 0.2 0.2
|
||||
( -112 -528 -80 ) ( -64 -528 -48 ) ( -64 -528 -112 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -528 -112 ) ( -64 -480 -112 ) ( -112 -480 -112 ) placeholder 64 160 0 0.2 0.2
|
||||
( -112 -480 -80 ) ( -64 -480 -80 ) ( -64 -528 -48 ) placeholder 64 160 0 0.2 0.2
|
||||
( -64 -480 -112 ) ( -64 -480 -80 ) ( -112 -480 -80 ) placeholder 64 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -64 -480 -80 ) ( -64 -480 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 19
|
||||
{
|
||||
( -64 -528 -48 ) ( -64 -576 -80 ) ( -64 -576 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
( -64 -576 -80 ) ( -16 -576 -80 ) ( -16 -576 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -16 -576 -80 ) ( -64 -576 -80 ) placeholder 80 176 0 0.2 0.2
|
||||
( -16 -576 -112 ) ( -16 -528 -112 ) ( -64 -528 -112 ) placeholder 80 176 0 0.2 0.2
|
||||
( -16 -528 -112 ) ( -16 -528 -80 ) ( -64 -528 -48 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -16 -528 -80 ) ( -16 -576 -80 ) placeholder 80 176 0 0.2 0.2
|
||||
( -16 -576 -80 ) ( -16 -528 -80 ) ( -16 -528 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 20
|
||||
{
|
||||
( -16 -384 -112 ) ( -16 -383 -112 ) ( -16 -384 -111 ) placeholder -192 -64 0 0.2 0.2
|
||||
( -16 -624 -112 ) ( -16 -624 -111 ) ( -15 -624 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -384 -112 ) ( -15 -384 -112 ) ( -16 -383 -112 ) placeholder -160 192 0 0.2 0.2
|
||||
( 112 -256 -80 ) ( 112 -255 -80 ) ( 113 -256 -80 ) placeholder -160 192 0 0.2 0.2
|
||||
( 112 -576 -80 ) ( 113 -576 -80 ) ( 112 -576 -79 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -256 -80 ) ( 32 -256 -79 ) ( 32 -255 -80 ) placeholder -192 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 21
|
||||
{
|
||||
( 32 -624 -80 ) ( 32 -672 -48 ) ( 32 -672 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 80 -672 -80 ) ( 80 -672 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -672 -112 ) ( 80 -624 -112 ) ( 32 -624 -112 ) placeholder -144 208 0 0.2 0.2
|
||||
( 32 -624 -80 ) ( 80 -624 -80 ) ( 32 -672 -48 ) placeholder -144 208 0 0.2 0.2
|
||||
( 80 -624 -112 ) ( 80 -624 -80 ) ( 32 -624 -80 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 32 -672 -48 ) ( 80 -624 -80 ) ( 80 -672 -80 ) placeholder -144 208 0 0.2 0.2
|
||||
( 80 -672 -80 ) ( 80 -624 -80 ) ( 80 -624 -112 ) placeholder -208 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 22
|
||||
{
|
||||
( -64 -480 -80 ) ( -64 -528 -48 ) ( -64 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -16 -528 -80 ) ( -16 -528 -112 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -16 -528 -112 ) ( -16 -480 -112 ) ( -64 -480 -112 ) placeholder 80 160 0 0.2 0.2
|
||||
( -64 -480 -80 ) ( -16 -480 -80 ) ( -64 -528 -48 ) placeholder 80 160 0 0.2 0.2
|
||||
( -16 -480 -112 ) ( -16 -480 -80 ) ( -64 -480 -80 ) placeholder 80 -64 0 0.2 0.2
|
||||
( -64 -528 -48 ) ( -16 -480 -80 ) ( -16 -528 -80 ) placeholder 80 160 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( -16 -480 -80 ) ( -16 -480 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 23
|
||||
{
|
||||
( -16 -528 -80 ) ( -16 -576 -80 ) ( -16 -576 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
( -16 -576 -80 ) ( 32 -576 -80 ) ( 32 -576 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -576 -112 ) ( 32 -528 -112 ) ( -16 -528 -112 ) placeholder -160 176 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( 32 -576 -80 ) ( -16 -576 -80 ) placeholder -160 176 0 0.2 0.2
|
||||
( 32 -528 -112 ) ( 32 -528 -96 ) ( -16 -528 -80 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( 32 -528 -96 ) ( 32 -576 -80 ) placeholder -160 176 0 0.2 0.2
|
||||
( 32 -576 -80 ) ( 32 -528 -96 ) ( 32 -528 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 24
|
||||
{
|
||||
( 32 -384 -112 ) ( 32 -383 -112 ) ( 32 -384 -111 ) placeholder -192 -64 0 0.2 0.2
|
||||
( 32 -624 -112 ) ( 32 -624 -111 ) ( 33 -624 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 32 -384 -112 ) ( 33 -384 -112 ) ( 32 -383 -112 ) placeholder -144 192 0 0.2 0.2
|
||||
( 160 -256 -80 ) ( 160 -255 -80 ) ( 161 -256 -80 ) placeholder -144 192 0 0.2 0.2
|
||||
( 160 -576 -80 ) ( 161 -576 -80 ) ( 160 -576 -79 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -256 -80 ) ( 80 -256 -79 ) ( 80 -255 -80 ) placeholder -192 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 25
|
||||
{
|
||||
( -16 -480 -80 ) ( -16 -528 -80 ) ( -16 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( 32 -528 -96 ) ( 32 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -528 -112 ) ( 32 -480 -112 ) ( -16 -480 -112 ) placeholder -160 160 0 0.2 0.2
|
||||
( -16 -480 -80 ) ( 32 -480 -80 ) ( -16 -528 -80 ) placeholder -160 160 0 0.2 0.2
|
||||
( 32 -480 -112 ) ( 32 -480 -80 ) ( -16 -480 -80 ) placeholder -160 -64 0 0.2 0.2
|
||||
( -16 -528 -80 ) ( 32 -480 -80 ) ( 32 -528 -96 ) placeholder -160 160 0 0.2 0.2
|
||||
( 32 -528 -96 ) ( 32 -480 -80 ) ( 32 -480 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 26
|
||||
{
|
||||
( 32 -528 -96 ) ( 32 -576 -80 ) ( 32 -576 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
( 32 -528 -96 ) ( 80 -528 -80 ) ( 32 -576 -80 ) placeholder -144 176 0 0.2 0.2
|
||||
( 32 -576 -80 ) ( 80 -576 -80 ) ( 80 -576 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -576 -112 ) ( 80 -528 -112 ) ( 32 -528 -112 ) placeholder -144 176 0 0.2 0.2
|
||||
( 32 -576 -80 ) ( 80 -528 -80 ) ( 80 -576 -80 ) placeholder -144 176 0 0.2 0.2
|
||||
( 80 -528 -112 ) ( 80 -528 -80 ) ( 32 -528 -96 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -576 -80 ) ( 80 -528 -80 ) ( 80 -528 -112 ) placeholder -176 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 27
|
||||
{
|
||||
( 32 -480 -80 ) ( 32 -528 -96 ) ( 32 -528 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
( 32 -480 -80 ) ( 80 -528 -80 ) ( 32 -528 -96 ) placeholder -144 160 0 0.2 0.2
|
||||
( 32 -528 -96 ) ( 80 -528 -80 ) ( 80 -528 -112 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -528 -112 ) ( 80 -480 -112 ) ( 32 -480 -112 ) placeholder -144 160 0 0.2 0.2
|
||||
( 32 -480 -80 ) ( 80 -480 -80 ) ( 80 -528 -80 ) placeholder -144 160 0 0.2 0.2
|
||||
( 80 -480 -112 ) ( 80 -480 -80 ) ( 32 -480 -80 ) placeholder -144 -64 0 0.2 0.2
|
||||
( 80 -528 -80 ) ( 80 -480 -80 ) ( 80 -480 -112 ) placeholder -160 -64 0 0.2 0.2
|
||||
}
|
||||
// brush 28
|
||||
{
|
||||
( 224 256 112 ) ( 224 257 112 ) ( 224 256 113 ) placeholder 170.66669 8 0 -0.375 0.4
|
||||
( 224 256 112 ) ( 224 256 113 ) ( 225 256 112 ) placeholder 224 -256 0 0.4 0.4
|
||||
( 224 256 112 ) ( 225 256 112 ) ( 224 257 112 ) placeholder 47.999992 138.99998 0 -0.4 0.4
|
||||
( 320 336 208 ) ( 320 337 208 ) ( 321 336 208 ) placeholder 208 112 0 0.4 0.4
|
||||
( 320 352 128 ) ( 321 352 128 ) ( 320 352 129 ) placeholder 48 -256 0 -0.4 0.4
|
||||
( 320 336 128 ) ( 320 336 129 ) ( 320 337 128 ) placeholder 128 -256 0 0.4 0.4
|
||||
( 36.28718707889797 0 4 ) ( 35.787187078897965 0.866025403784441 4 ) ( 36.28718707889797 0 5 ) 256_cement_dirty_rough_wall 0 0 0 1 1
|
||||
( 22.430780618346965 -8 4 ) ( 22.430780618346965 -8 5 ) ( 21.56475521456251 -8.499999999999979 4 ) 256_cement_dirty_rough_wall 0 0 0 1 1
|
||||
( 113.56921938165306 26.14359353944895 -16 ) ( 113.06921938165306 27.0096189432334 -16 ) ( 112.70319397786861 25.643593539448965 -16 ) 256_cement_dirty_rough_wall 0 0 0 1 1
|
||||
( 22.430780618346965 -8 80 ) ( 21.56475521456251 -8.499999999999979 80 ) ( 21.930780618346944 -7.1339745962155305 80 ) 256_cement_dirty_rough_wall 0 0 0 1 1
|
||||
( 113.56921938165306 26.14359353944895 -16 ) ( 112.70319397786861 25.643593539448965 -16 ) ( 113.56921938165306 26.14359353944895 -15 ) 256_cement_dirty_rough_wall 0 0 0 1 1
|
||||
( 99.71281292110204 18.143593539448965 -16 ) ( 99.71281292110204 18.143593539448965 -15 ) ( 99.21281292110204 19.009618943233413 -16 ) 256_cement_dirty_rough_wall 0 0 0 1 1
|
||||
}
|
||||
}
|
||||
// entity 1
|
||||
{
|
||||
"classname" "info_player_start"
|
||||
"origin" "112 -48 56"
|
||||
"origin" "2 52 8"
|
||||
}
|
||||
|
||||
@@ -50,3 +50,8 @@ actions = [
|
||||
actions = [
|
||||
"debug_log Person hat sich verabschiedet",
|
||||
]
|
||||
|
||||
[tiffany]
|
||||
actions = [
|
||||
"start_ink cinema.ink.json",
|
||||
]
|
||||
|
||||
|
After Width: | Height: | Size: 192 KiB |
|
After Width: | Height: | Size: 256 KiB |
|
After Width: | Height: | Size: 256 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,18 @@
|
||||
# Assets
|
||||
|
||||
## Texturen
|
||||
https://www.manytextures.com/
|
||||
https://opengameart.org/content/arcade-carpet-textures
|
||||
|
||||
|
||||
# Tutorials
|
||||
|
||||
## Blender
|
||||
|
||||
Blender UI Base Setup + Tips
|
||||
https://www.youtube.com/watch?v=sZeGW0otiJI
|
||||
|
||||
Rigging in PS1 Look:
|
||||
https://www.youtube.com/watch?v=puwu9hZmQYI
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ fn prompt_for(mode: &Mode) -> &'static str {
|
||||
match mode {
|
||||
Mode::Dialog(d) if !d.choices.is_empty() => " wahl> ",
|
||||
Mode::Dialog(_) => " [Enter] ",
|
||||
Mode::Menu => "menü> ",
|
||||
Mode::FirstPerson => "spiel> ",
|
||||
Mode::Free => "> ",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
//! Brush-Collision: Swept-AABB-Trace gegen konvexe Brushes (Quake-Hull-Idee).
|
||||
//!
|
||||
//! Ein Brush ist der Schnitt seiner Halbräume `{ n·x ≤ d }` (n nach außen) —
|
||||
//! genau die Ebenen, die auch render::brush rekonstruiert, hier aber in
|
||||
//! **Engine-Koords** und **vollständig** (keine Koplanar-Elimination, kein
|
||||
//! Culling: für Collision zählt das ganze solide Volumen).
|
||||
//!
|
||||
//! Die Box wird nicht selbst getract, sondern per Minkowski-Aufblasung in den
|
||||
//! Ebenen versenkt: jede Ebene rückt um die auf ihre Normale projizierte
|
||||
//! Box-Halbgröße nach außen (`d' = d + |n|·half`). Damit wird Box-vs-Brush zur
|
||||
//! Punkt-vs-aufgeblasener-Brush-Frage, und die Trace ist ein simpler
|
||||
//! Halbraum-Clip des Segments (Eintritts-/Austritts-Bruch). Ein `SKIN` hält
|
||||
//! den Mittelpunkt eine Haaresbreite vor der Fläche, damit der Folgeframe nicht
|
||||
//! sofort wieder im Kontakt steckt.
|
||||
//!
|
||||
//! Headless wie der Rest von `engine`: hängt nur an `map` (für die Brush-Ebenen
|
||||
//! und die geteilte Koordinaten-Umrechnung). Der Player ruft `trace` in
|
||||
//! `player::step`; gebaut wird die Welt einmal vom Renderer aus der `Map`.
|
||||
|
||||
use crate::engine::map::{self, Map};
|
||||
|
||||
/// Mindestabstand (units), den der Box-Mittelpunkt vor einer Fläche hält —
|
||||
/// verhindert Re-Kollision/Jitter im Folgeframe. ~1 cm, unsichtbar.
|
||||
const SKIN: f32 = 0.01;
|
||||
|
||||
/// Eine nach außen orientierte Ebene `n·x ≤ d` (innen = Halbraum).
|
||||
pub(crate) struct Plane {
|
||||
pub(crate) n: [f32; 3],
|
||||
pub(crate) d: f32,
|
||||
}
|
||||
|
||||
/// Ein konvexer Brush = Schnitt seiner Halbräume.
|
||||
struct ConvexBrush {
|
||||
planes: Vec<Plane>,
|
||||
}
|
||||
|
||||
/// Die statische Kollisionswelt: alle soliden Brushes der Map.
|
||||
pub struct CollisionWorld {
|
||||
brushes: Vec<ConvexBrush>,
|
||||
}
|
||||
|
||||
/// Ergebnis einer Trace: Bruchteil entlang des Segments bis zum Kontakt und
|
||||
/// die nach außen zeigende Trefferebenen-Normale (zum Gleiten/Boden-Erkennen).
|
||||
pub struct Hit {
|
||||
pub frac: f32,
|
||||
pub normal: [f32; 3],
|
||||
}
|
||||
|
||||
impl CollisionWorld {
|
||||
/// Kollisionswelt aus allen Brushes der Map bauen. Jede Brush-Face liefert
|
||||
/// eine Ebene (aus drei Punkten, nach Engine-Koords gedreht).
|
||||
pub fn build(world: &Map) -> Self {
|
||||
let mut brushes = Vec::new();
|
||||
for ent in &world.entities {
|
||||
for b in &ent.brushes {
|
||||
if b.faces.len() < 4 { continue; } // kein geschlossenes Volumen
|
||||
let planes = b.faces.iter().map(|f| plane_from(&f.plane)).collect();
|
||||
brushes.push(ConvexBrush { planes });
|
||||
}
|
||||
}
|
||||
Self { brushes }
|
||||
}
|
||||
|
||||
/// Leere Welt (keine Brushes) — Default, bis eine Map geladen ist.
|
||||
pub fn empty() -> Self {
|
||||
Self { brushes: Vec::new() }
|
||||
}
|
||||
|
||||
/// Eine AABB (Halbmaße `half`) von `start` nach `end` (Box-Mittelpunkte)
|
||||
/// sweepen. Liefert den frühesten Kontakt über alle Brushes, sonst `None`.
|
||||
pub fn trace(&self, start: [f32; 3], end: [f32; 3], half: [f32; 3]) -> Option<Hit> {
|
||||
let mut nearest: Option<Hit> = None;
|
||||
for b in &self.brushes {
|
||||
if let Some(h) = trace_brush(b, start, end, half) {
|
||||
if nearest.as_ref().map_or(true, |n| h.frac < n.frac) {
|
||||
nearest = Some(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
nearest
|
||||
}
|
||||
}
|
||||
|
||||
/// Segment `start→end` gegen einen aufgeblasenen konvexen Brush clippen.
|
||||
/// `None`, wenn das Segment den Brush verfehlt oder der Start schon drin steckt
|
||||
/// (dann nicht blocken — sonst bliebe der Player hängen).
|
||||
fn trace_brush(b: &ConvexBrush, start: [f32; 3], end: [f32; 3], half: [f32; 3]) -> Option<Hit> {
|
||||
let mut enter = f32::NEG_INFINITY; // größter Eintritts-Bruch
|
||||
let mut leave = 1.0f32; // kleinster Austritts-Bruch
|
||||
let mut normal = [0.0f32; 3];
|
||||
let mut entered = false; // überhaupt eine Eintrittsebene gefunden?
|
||||
let mut started_outside = false;
|
||||
|
||||
for p in &b.planes {
|
||||
// Ebene um die Box-Halbgröße nach außen aufblasen (Minkowski).
|
||||
let d = p.d + p.n[0].abs() * half[0] + p.n[1].abs() * half[1] + p.n[2].abs() * half[2];
|
||||
let ds = dot(p.n, start) - d;
|
||||
let de = dot(p.n, end) - d;
|
||||
|
||||
if ds > 0.0 { started_outside = true; }
|
||||
if ds > 0.0 && de > 0.0 { return None; } // ganz außerhalb dieser Ebene
|
||||
if ds <= 0.0 && de <= 0.0 { continue; } // ganz innerhalb dieser Ebene
|
||||
|
||||
if ds > de {
|
||||
// Eintritt (außen → innen): SKIN-Rückzug, damit der Mittelpunkt
|
||||
// knapp vor der Fläche stoppt. `enter` darf dabei leicht negativ
|
||||
// werden (Kontakt liegt im SKIN-Band) — das wird unten auf 0
|
||||
// geklemmt, nicht verworfen, sonst rutschte ein ruhender Körper
|
||||
// im Folgeframe durch die Fläche.
|
||||
let f = (ds - SKIN) / (ds - de);
|
||||
if f > enter { enter = f; normal = p.n; entered = true; }
|
||||
} else {
|
||||
// Austritt (innen → außen).
|
||||
let f = ds / (ds - de);
|
||||
if f < leave { leave = f; }
|
||||
}
|
||||
}
|
||||
|
||||
// Treffer nur, wenn der Start außerhalb lag (sonst säße man fest), eine
|
||||
// Eintrittsebene existiert und das Eintrittsintervall vor dem Segmentende
|
||||
// beginnt.
|
||||
if started_outside && entered && enter < leave && enter < 1.0 {
|
||||
Some(Hit { frac: enter.max(0.0), normal })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Ebene aus drei Face-Punkten (Quake-Reihenfolge), gedreht nach Engine-Koords.
|
||||
/// Wie render::brush::plane, aber direkt im Engine-System: weil `to_engine` die
|
||||
/// Orientierung erhält (det +1), zeigt `cross(c−a, b−a)` weiter nach außen.
|
||||
fn plane_from(p: &[[f32; 3]; 3]) -> Plane {
|
||||
let a = map::to_engine(p[0]);
|
||||
let b = map::to_engine(p[1]);
|
||||
let c = map::to_engine(p[2]);
|
||||
let n = normalize(cross(sub(c, a), sub(b, a)));
|
||||
Plane { n, d: dot(n, a) }
|
||||
}
|
||||
|
||||
// --- kleine Vektor-Helfer (privat, wie render::brush; ein gemeinsames
|
||||
// engine::vec3 lohnt erst, falls ein dritter Nutzer auftaucht) ---------------
|
||||
|
||||
fn sub(a: [f32; 3], b: [f32; 3]) -> [f32; 3] { [a[0] - b[0], a[1] - b[1], a[2] - b[2]] }
|
||||
fn dot(a: [f32; 3], b: [f32; 3]) -> f32 { a[0] * b[0] + a[1] * b[1] + a[2] * b[2] }
|
||||
|
||||
fn cross(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
|
||||
[a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]
|
||||
}
|
||||
|
||||
fn normalize(a: [f32; 3]) -> [f32; 3] {
|
||||
let len = dot(a, a).sqrt();
|
||||
if len > 0.0 { [a[0] / len, a[1] / len, a[2] / len] } else { a }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn aabb_brush(min: [f32; 3], max: [f32; 3]) -> Vec<Plane> {
|
||||
vec![
|
||||
Plane { n: [ 1.0, 0.0, 0.0], d: max[0] }, Plane { n: [-1.0, 0.0, 0.0], d: -min[0] },
|
||||
Plane { n: [0.0, 1.0, 0.0], d: max[1] }, Plane { n: [0.0, -1.0, 0.0], d: -min[1] },
|
||||
Plane { n: [0.0, 0.0, 1.0], d: max[2] }, Plane { n: [0.0, 0.0, -1.0], d: -min[2] },
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn from_brushes(brushes: Vec<Vec<Plane>>) -> CollisionWorld {
|
||||
CollisionWorld {
|
||||
brushes: brushes.into_iter().map(|planes| ConvexBrush { planes }).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// Punktförmige Box (half=0) → reine Strahl-vs-Brush-Trace.
|
||||
const PT: [f32; 3] = [0.0, 0.0, 0.0];
|
||||
|
||||
#[test]
|
||||
fn trace_hits_near_face_with_normal() {
|
||||
let w = from_brushes(vec![aabb_brush([0.0, 0.0, 0.0], [2.0, 2.0, 2.0])]);
|
||||
// Von x=-1 nach x=3 (Gesamtweg 4), trifft die −X-Fläche bei x≈0.
|
||||
let h = w.trace([-1.0, 1.0, 1.0], [3.0, 1.0, 1.0], PT).unwrap();
|
||||
assert!((h.frac - 0.25).abs() < 0.02, "frac={}", h.frac);
|
||||
assert!(h.normal[0] < -0.5, "normal sollte -X sein: {:?}", h.normal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trace_misses_returns_none() {
|
||||
let w = from_brushes(vec![aabb_brush([0.0, 0.0, 0.0], [2.0, 2.0, 2.0])]);
|
||||
// Läuft oberhalb der Box vorbei.
|
||||
assert!(w.trace([-1.0, 5.0, 1.0], [3.0, 5.0, 1.0], PT).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trace_starting_inside_does_not_block() {
|
||||
let w = from_brushes(vec![aabb_brush([0.0, 0.0, 0.0], [2.0, 2.0, 2.0])]);
|
||||
// Start mitten im Brush → kein Hit (sonst säße man fest).
|
||||
assert!(w.trace([1.0, 1.0, 1.0], [5.0, 1.0, 1.0], PT).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aabb_expands_by_half_extents() {
|
||||
let w = from_brushes(vec![aabb_brush([0.0, 0.0, 0.0], [2.0, 2.0, 2.0])]);
|
||||
// Box mit halber Breite 0.5: Kontakt schon bei x≈-0.5 statt 0.
|
||||
let h = w.trace([-2.0, 1.0, 1.0], [2.0, 1.0, 1.0], [0.5, 0.5, 0.5]).unwrap();
|
||||
// Weg 4, Kontakt bei x≈-0.5 → frac≈(−0.5−(−2))/4 = 0.375.
|
||||
assert!((h.frac - 0.375).abs() < 0.02, "frac={}", h.frac);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_brushes_from_map() {
|
||||
// Ein achsenparalleler Quader-Brush (Quake-Koords).
|
||||
let src = r#"
|
||||
{
|
||||
"classname" "worldspawn"
|
||||
{
|
||||
( 0 0 0 ) ( 0 1 0 ) ( 0 0 1 ) t 0 0 0 1 1
|
||||
( 0 0 0 ) ( 0 0 1 ) ( 1 0 0 ) t 0 0 0 1 1
|
||||
( 0 0 0 ) ( 1 0 0 ) ( 0 1 0 ) t 0 0 0 1 1
|
||||
( 64 64 64 ) ( 64 65 64 ) ( 65 64 64 ) t 0 0 0 1 1
|
||||
( 64 64 64 ) ( 65 64 64 ) ( 64 64 65 ) t 0 0 0 1 1
|
||||
( 64 64 64 ) ( 64 64 65 ) ( 64 65 64 ) t 0 0 0 1 1
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let m = map::parse(src);
|
||||
let w = CollisionWorld::build(&m);
|
||||
assert_eq!(w.brushes.len(), 1);
|
||||
assert_eq!(w.brushes[0].planes.len(), 6);
|
||||
// Engine-Koords des Quake-Würfels [0,64]³: x[0,2], y[0,2], z[-2,0]
|
||||
// (Drehung (x,z,−y)·1/32). Strahl von außerhalb (−X) hindurch.
|
||||
let h = w.trace([-1.0, 1.0, -1.0], [3.0, 1.0, -1.0], PT);
|
||||
assert!(h.is_some(), "Strahl sollte den Brush treffen");
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,44 @@ pub enum Action {
|
||||
HideObject(String),
|
||||
/// WAV unter `assets/audio/<name>` als SFX abspielen.
|
||||
PlaySound(String),
|
||||
/// Anzeige-/Eingabemodus wechseln (Spiel, Flycam, Menü). Vom Frontend
|
||||
/// auf seinen `Mode` gemappt; der Dialog-Modus ist hier bewusst nicht
|
||||
/// wählbar — der entsteht nur aus dem Story-Ablauf.
|
||||
SetMode(ModeTarget),
|
||||
}
|
||||
|
||||
/// Frontend-neutrales Ziel eines Moduswechsels. Spiegelt die nicht-Dialog-
|
||||
/// Varianten von `session::Mode`, ohne dass der Kern die rich `Mode`-Daten
|
||||
/// (Dialog-State) kennen muss. So bleibt `game` ← `session` eine Einbahnstraße.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ModeTarget {
|
||||
/// First-Person-Spiel.
|
||||
Play,
|
||||
/// Noclip-Debug-Flycam.
|
||||
Free,
|
||||
/// Menü (pausiert die Welt, gibt die Maus frei).
|
||||
Menu,
|
||||
}
|
||||
|
||||
impl ModeTarget {
|
||||
/// `play`/`free`/`menu` (case-insensitiv) → Ziel; sonst `None`.
|
||||
pub fn parse(s: &str) -> Option<Self> {
|
||||
match s.trim().to_ascii_lowercase().as_str() {
|
||||
"play" => Some(Self::Play),
|
||||
"free" => Some(Self::Free),
|
||||
"menu" => Some(Self::Menu),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Kanonischer Name fürs Echo/Logging.
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Play => "play",
|
||||
Self::Free => "free",
|
||||
Self::Menu => "menu",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Game {
|
||||
|
||||
@@ -26,9 +26,8 @@ pub struct Entity {
|
||||
}
|
||||
|
||||
impl Entity {
|
||||
// Wird beim Entity-/Metadaten-Schritt gebraucht (worldspawn vs. Props,
|
||||
// Signal-Bindung); bis dahin nur in Tests verwendet.
|
||||
#[allow(dead_code)]
|
||||
// Genutzt für Spawn-Auflösung (info_player_start) und künftige
|
||||
// Entity-/Signal-Bindung.
|
||||
pub fn classname(&self) -> Option<&str> {
|
||||
self.props.get("classname").map(String::as_str)
|
||||
}
|
||||
@@ -52,6 +51,18 @@ pub struct Face {
|
||||
pub scale: [f32; 2],
|
||||
}
|
||||
|
||||
/// Quake-Einheiten pro Engine-Einheit (32 ≈ klassische „1 Meter"-Annahme).
|
||||
/// Single Source für Render- (render::brush) und Collision-Pfad
|
||||
/// (engine::collision), damit beide denselben Maßstab benutzen.
|
||||
pub const MAP_SCALE: f32 = 1.0 / 32.0;
|
||||
|
||||
/// Quake-Koords (Z-up) → Engine-Koords (Y-up, Blick −Z), skaliert:
|
||||
/// `(x, y, z) → (x, z, −y) · MAP_SCALE`. Die Drehung erhält die Orientierung
|
||||
/// (det +1) — nach außen zeigende Normalen bleiben außen.
|
||||
pub fn to_engine(p: [f32; 3]) -> [f32; 3] {
|
||||
[p[0] * MAP_SCALE, p[2] * MAP_SCALE, -p[1] * MAP_SCALE]
|
||||
}
|
||||
|
||||
pub fn load(path: &str) -> Map {
|
||||
let src = read_to_string(path).unwrap_or_else(|e| panic!("map load {path}: {e}"));
|
||||
parse(&src)
|
||||
|
||||
@@ -20,12 +20,18 @@
|
||||
//! nichts) — die geteilte Heimat für Format-Dekodierung, die jedes Frontend
|
||||
//! per Pull konsumiert. `map` ist zugleich der Anfang des headless
|
||||
//! Datenmodells (Phase 2 des Renderer-Plans).
|
||||
//!
|
||||
//! `player` ist die First-Person-Physik, aus der der Renderer seine View
|
||||
//! ableitet; `collision` (hängt an `map`) liefert ihr die Brush-Welt für den
|
||||
//! Swept-AABB-Trace in `player::step`. Beide bleiben headless.
|
||||
|
||||
pub mod assets;
|
||||
pub mod collision;
|
||||
pub mod game;
|
||||
pub mod ink;
|
||||
pub mod kv;
|
||||
pub mod map;
|
||||
pub mod player;
|
||||
pub mod signals;
|
||||
pub mod story_ctrl;
|
||||
pub mod tga;
|
||||
|
||||
@@ -0,0 +1,479 @@
|
||||
//! First-Person-Player: headless Bewegungsphysik (Stufe 1).
|
||||
//!
|
||||
//! Bewusst frontend- und GPU-frei — wie der Rest von `engine`. Der Renderer
|
||||
//! *leitet* seine View aus dem Player ab (`Mat4::view(pos + eye, yaw, pitch)`),
|
||||
//! der Player kennt den Renderer nie. Damit bleibt die Physik im Terminal
|
||||
//! testbar, und die Schichtrichtung (`engine ← render`) kehrt sich nicht um.
|
||||
//!
|
||||
//! Keine neuen Dependencies: `step` braucht nur `sin_cos` und Komponenten-
|
||||
//! Arithmetik. Vektor-Helfer werden nicht geteilt (brush.rs hält seine eigenen
|
||||
//! privat) — erst wenn Stufe 2 (Brush-Collision) echte dot/cross/normalize an
|
||||
//! mehreren Stellen braucht, lohnt ein gemeinsames Modul.
|
||||
//!
|
||||
//! Collision (Stufe 3): `step` sweept die Spieler-AABB gegen die
|
||||
//! [`CollisionWorld`] (Brushes) und gleitet an Treffern entlang (move-and-slide).
|
||||
//! Getract wird der Box-Mittelpunkt (Fußpunkt + halbe Höhe), danach geht es
|
||||
//! zurück auf den Fußpunkt. Bodenkontakt fällt aus nach oben zeigenden
|
||||
//! Trefferflächen ab.
|
||||
//!
|
||||
//! Konvention wie `render::math::view`/`camera.rs`: yaw=0 blickt −Z, positiver
|
||||
//! Yaw dreht nach links, positiver Pitch hebt den Blick. Die Bewegung ist
|
||||
//! horizontal aus dem Yaw (ohne Pitch) — Hochschauen lässt einen nicht abheben.
|
||||
|
||||
use crate::engine::collision::CollisionWorld;
|
||||
|
||||
/// Augenhöhe über dem Fußpunkt (units). Der Renderer setzt die Kamera auf
|
||||
/// `pos + [0, EYE_HEIGHT, 0]`. 1 Engine-Unit ≈ 1 m (siehe map::MAP_SCALE).
|
||||
pub const EYE_HEIGHT: f32 = 1.6;
|
||||
|
||||
/// Fallbeschleunigung (units/s²). Über realem g (9.81) für knackiges
|
||||
/// Spielgefühl — wie die meisten Shooter. Tuning-Wert.
|
||||
const GRAVITY: f32 = 20.0;
|
||||
/// Laufgeschwindigkeit (units/s). 1 Engine-Unit ≈ 1 m (siehe map::MAP_SCALE).
|
||||
const WALK_SPEED: f32 = 5.0;
|
||||
/// Absprunggeschwindigkeit (units/s) — bestimmt die Sprunghöhe. Tuning-Wert.
|
||||
const JUMP_SPEED: f32 = 7.0;
|
||||
/// Knapp unter 90°: hält den Blick aus der Senkrechten (wie `camera.rs`).
|
||||
const PITCH_LIMIT: f32 = 1.55;
|
||||
/// Halbmaße der Spieler-AABB (units): 0.6 m breit/tief, 1.8 m hoch.
|
||||
const HALF_EXTENTS: [f32; 3] = [0.3, 0.9, 0.3];
|
||||
/// Trefferflächen mit Normalen-Y darüber gelten als Boden (~45°-Rampen ok).
|
||||
const GROUND_NORMAL_Y: f32 = 0.7;
|
||||
/// Slide-Iterationen pro Schritt (Wände, Ecken, Boden zugleich).
|
||||
const MAX_SLIDES: u32 = 4;
|
||||
/// Wie weit der Ground-Snap nach unten sucht (units). Überbrückt Stufen,
|
||||
/// abwärts-Slopes und Brush-Nähte, ohne echte Abgründe zu „verschlucken".
|
||||
const SNAP_DIST: f32 = 0.5;
|
||||
/// Maximale Stufenhöhe, die der Spieler ohne Sprung übersteigt (units).
|
||||
const STEP_HEIGHT: f32 = 0.5;
|
||||
|
||||
pub struct Player {
|
||||
/// Fußpunkt in Engine-Koords (Y-up).
|
||||
pub pos: [f32; 3],
|
||||
/// Geschwindigkeit in units/s (Y trägt Gravitation/Sprung).
|
||||
pub vel: [f32; 3],
|
||||
pub yaw: f32,
|
||||
pub pitch: f32,
|
||||
/// Steht auf Boden? Setzt der Boden-/Collision-Schritt; Gattung für Sprung.
|
||||
pub grounded: bool,
|
||||
}
|
||||
|
||||
impl Player {
|
||||
pub fn new(pos: [f32; 3]) -> Self {
|
||||
Self { pos, vel: [0.0; 3], yaw: 0.0, pitch: 0.0, grounded: false }
|
||||
}
|
||||
|
||||
/// Roh-Maus-Delta (Pixel) → Yaw/Pitch, identisch zu `camera::apply_mouse`,
|
||||
/// damit Bewegung und Bild dieselbe Konvention teilen.
|
||||
pub fn look(&mut self, dx: f32, dy: f32, sens: f32) {
|
||||
self.yaw -= dx * sens;
|
||||
self.pitch = (self.pitch - dy * sens).clamp(-PITCH_LIMIT, PITCH_LIMIT);
|
||||
}
|
||||
|
||||
/// Einen Physikschritt integrieren. `world` ist die Brush-Kollisionswelt,
|
||||
/// `fwd`/`right` Tastenachsen in [-1, 1], `jump` ein Flankensignal (true =
|
||||
/// Sprungtaste diesen Frame), `dt` die Frame-Zeit in Sekunden.
|
||||
pub fn step(&mut self, world: &CollisionWorld, fwd: f32, right: f32, jump: bool, dt: f32) {
|
||||
let was_grounded = self.grounded;
|
||||
|
||||
// Horizontale Basis aus dem Yaw (ohne Pitch): forward = -Z bei yaw=0,
|
||||
// right = +X bei yaw=0 — exakt wie `camera::forward`/`right` flach.
|
||||
let (sy, cy) = self.yaw.sin_cos();
|
||||
let f = [-sy, -cy]; // (x, z)
|
||||
let r = [ cy, -sy]; // (x, z)
|
||||
|
||||
// Wunschgeschwindigkeit sofort setzen (ohne Beschleunigung/Reibung —
|
||||
// das ist Feintuning für später). Volle Luftkontrolle, ebenfalls vorerst.
|
||||
self.vel[0] = (f[0] * fwd + r[0] * right) * WALK_SPEED;
|
||||
self.vel[2] = (f[1] * fwd + r[1] * right) * WALK_SPEED;
|
||||
|
||||
// Sprung vor der Gravitation, nur vom Boden (Vorframe-`grounded`) —
|
||||
// kein Doppelsprung.
|
||||
let jumped = jump && was_grounded;
|
||||
if jumped {
|
||||
self.vel[1] = JUMP_SPEED;
|
||||
self.grounded = false;
|
||||
}
|
||||
self.vel[1] -= GRAVITY * dt;
|
||||
|
||||
self.move_and_slide(world, dt);
|
||||
|
||||
// Ground-Snap: war der Spieler eben noch am Boden, ist nicht gesprungen
|
||||
// und steht nach dem Zug knapp in der Luft (abwärts-Slope, Stufe runter,
|
||||
// Brush-Naht), ziehen wir ihn auf den Boden darunter — so klebt er über
|
||||
// Nähte hinweg, statt kurz luftig darüber zu schweben. Nicht beim
|
||||
// Aufsteigen (vel.y > 0), damit Rampen-Absprünge erhalten bleiben.
|
||||
if was_grounded && !jumped && !self.grounded && self.vel[1] <= 0.0 {
|
||||
self.snap_to_ground(world);
|
||||
}
|
||||
}
|
||||
|
||||
/// Die Box senkrecht bis zu [`SNAP_DIST`] nach unten tracen; trifft sie
|
||||
/// dabei echten Boden, den Spieler dorthin heften und `grounded` setzen.
|
||||
fn snap_to_ground(&mut self, world: &CollisionWorld) {
|
||||
let half = HALF_EXTENTS;
|
||||
let center = [self.pos[0], self.pos[1] + half[1], self.pos[2]];
|
||||
let down = [center[0], center[1] - SNAP_DIST, center[2]];
|
||||
let Some(hit) = world.trace(center, down, half) else { return; };
|
||||
if hit.normal[1] <= GROUND_NORMAL_Y { return; } // nur Boden, keine Wand
|
||||
self.pos[1] = (center[1] - SNAP_DIST * hit.frac) - half[1];
|
||||
self.grounded = true;
|
||||
if self.vel[1] < 0.0 { self.vel[1] = 0.0; }
|
||||
}
|
||||
|
||||
/// Den Box-Mittelpunkt gegen die Welt bewegen, mit Step-up. Aktualisiert
|
||||
/// `pos`, `vel` und `grounded`.
|
||||
///
|
||||
/// Zuerst ein flacher Slide. Steht der Spieler am Boden, wird zusätzlich
|
||||
/// der Step-up probiert: hoch um [`STEP_HEIGHT`], horizontal sliden, wieder
|
||||
/// runter auf Boden. Kommt das horizontal weiter als der flache Zug und
|
||||
/// endet auf begehbarem Boden, gewinnt es. Das hebt die flache Box-
|
||||
/// Unterkante über Brush-Nähte (Slope-Kanten, Stufen), an denen sie sonst
|
||||
/// als „Wand" verkantet — der Grund, warum man ohne Step-up weder auf eine
|
||||
/// Slope hinauf- noch (wegen der vertikalen Prismen-Seitenfläche) hinabkam.
|
||||
fn move_and_slide(&mut self, world: &CollisionWorld, dt: f32) {
|
||||
let half = HALF_EXTENTS;
|
||||
let was_grounded = self.grounded; // in step() vor dem Sprung gesetzt
|
||||
let start = [self.pos[0], self.pos[1] + half[1], self.pos[2]];
|
||||
|
||||
let (flat_c, flat_v, flat_g) = slide(world, start, self.vel, dt);
|
||||
let (mut best_c, mut best_v, mut best_g) = (flat_c, flat_v, flat_g);
|
||||
|
||||
if was_grounded {
|
||||
let (up, _) = trace_step(world, start, [0.0, STEP_HEIGHT, 0.0]);
|
||||
let (stepped, step_v, _) = slide(world, up, self.vel, dt);
|
||||
let down_dist = (up[1] - start[1]) + STEP_HEIGHT;
|
||||
let (landed, normal) = trace_step(world, stepped, [0.0, -down_dist, 0.0]);
|
||||
let on_ground = normal.is_some_and(|n| n[1] > GROUND_NORMAL_Y);
|
||||
if on_ground && horiz_dist2(landed, start) > horiz_dist2(flat_c, start) + 1e-6 {
|
||||
best_c = landed;
|
||||
best_v = step_v;
|
||||
best_g = true;
|
||||
}
|
||||
}
|
||||
|
||||
self.pos = [best_c[0], best_c[1] - half[1], best_c[2]];
|
||||
self.vel = best_v;
|
||||
self.grounded = best_g;
|
||||
}
|
||||
}
|
||||
|
||||
/// Reine Slide-Bewegung des Box-Mittelpunkts entlang `vel·dt` (keine Stufen):
|
||||
/// bis zu [`MAX_SLIDES`] Iterationen, an Treffern entlanggleitend. Gibt
|
||||
/// Endpunkt, Restgeschwindigkeit und Bodenkontakt zurück.
|
||||
fn slide(world: &CollisionWorld, mut center: [f32; 3], mut vel: [f32; 3], dt: f32) -> ([f32; 3], [f32; 3], bool) {
|
||||
let half = HALF_EXTENTS;
|
||||
let mut grounded = false;
|
||||
let mut remaining = dt;
|
||||
for _ in 0..MAX_SLIDES {
|
||||
if remaining <= 0.0 { break; }
|
||||
let disp = [vel[0] * remaining, vel[1] * remaining, vel[2] * remaining];
|
||||
let end = [center[0] + disp[0], center[1] + disp[1], center[2] + disp[2]];
|
||||
let Some(hit) = world.trace(center, end, half) else {
|
||||
center = end; // freie Bahn — Rest des Schritts ausführen
|
||||
break;
|
||||
};
|
||||
for i in 0..3 { center[i] += disp[i] * hit.frac; }
|
||||
remaining *= 1.0 - hit.frac;
|
||||
if hit.normal[1] > GROUND_NORMAL_Y { grounded = true; }
|
||||
let into = vel[0] * hit.normal[0] + vel[1] * hit.normal[1] + vel[2] * hit.normal[2];
|
||||
for i in 0..3 { vel[i] -= hit.normal[i] * into; }
|
||||
}
|
||||
(center, vel, grounded)
|
||||
}
|
||||
|
||||
/// Den Mittelpunkt um `delta` (ein Achsen-Versatz) bewegen, an Geometrie
|
||||
/// gestoppt. Gibt den erreichten Punkt und — falls gestoppt — die Treffer-
|
||||
/// Normale zurück.
|
||||
fn trace_step(world: &CollisionWorld, from: [f32; 3], delta: [f32; 3]) -> ([f32; 3], Option<[f32; 3]>) {
|
||||
let to = [from[0] + delta[0], from[1] + delta[1], from[2] + delta[2]];
|
||||
match world.trace(from, to, HALF_EXTENTS) {
|
||||
None => (to, None),
|
||||
Some(hit) => (
|
||||
[from[0] + delta[0] * hit.frac, from[1] + delta[1] * hit.frac, from[2] + delta[2] * hit.frac],
|
||||
Some(hit.normal),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Quadrierte horizontale Distanz (X/Z) — fürs „weiter gekommen?"-Vergleichen.
|
||||
fn horiz_dist2(a: [f32; 3], b: [f32; 3]) -> f32 {
|
||||
(a[0] - b[0]).powi(2) + (a[2] - b[2]).powi(2)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::engine::collision::{self, CollisionWorld};
|
||||
|
||||
/// Leere Welt (kein Boden) → freier Fall, gut für Luft-Tests.
|
||||
fn void() -> CollisionWorld {
|
||||
collision::from_brushes(vec![])
|
||||
}
|
||||
|
||||
/// Großer Boden-Brush mit Oberkante bei y=0.
|
||||
fn floor() -> CollisionWorld {
|
||||
collision::from_brushes(vec![
|
||||
collision::aabb_brush([-50.0, -1.0, -50.0], [50.0, 0.0, 50.0]),
|
||||
])
|
||||
}
|
||||
|
||||
/// Brush-Naht mit kleinem Höhensprung: obere Fläche (Oberkante y=0.3, x≤0)
|
||||
/// trifft auf untere (Oberkante y=0, x≥0). Sprung 0.3 < SNAP_DIST.
|
||||
fn step_down() -> CollisionWorld {
|
||||
collision::from_brushes(vec![
|
||||
collision::aabb_brush([-50.0, -1.0, -50.0], [0.0, 0.3, 50.0]),
|
||||
collision::aabb_brush([0.0, -1.0, -50.0], [50.0, 0.0, 50.0]),
|
||||
])
|
||||
}
|
||||
|
||||
/// Wie `step_down`, aber der Absatz ist 2.0 tief — größer als SNAP_DIST,
|
||||
/// also ein echter Abgrund.
|
||||
fn tall_ledge() -> CollisionWorld {
|
||||
collision::from_brushes(vec![
|
||||
collision::aabb_brush([-50.0, -1.0, -50.0], [0.0, 0.0, 50.0]),
|
||||
collision::aabb_brush([0.0, -3.0, -50.0], [50.0, -2.0, 50.0]),
|
||||
])
|
||||
}
|
||||
|
||||
/// Solides Dreiecksprisma (Slope steigt von (x=0,y=0) auf (x=2,y=1)) plus
|
||||
/// untere Fläche (Oberkante y=0, x≤0), die am Slope-Fuß bündig anschließt.
|
||||
fn ramp() -> CollisionWorld {
|
||||
let s = 5.0f32.sqrt();
|
||||
collision::from_brushes(vec![
|
||||
vec![
|
||||
collision::Plane { n: [-1.0 / s, 2.0 / s, 0.0], d: 0.0 }, // Slope-Oberfläche
|
||||
collision::Plane { n: [0.0, -1.0, 0.0], d: 0.0 }, // Unterseite (y≥0)
|
||||
collision::Plane { n: [1.0, 0.0, 0.0], d: 2.0 }, // x≤2
|
||||
collision::Plane { n: [-1.0, 0.0, 0.0], d: 0.0 }, // x≥0 (Slope-Fuß)
|
||||
collision::Plane { n: [0.0, 0.0, 1.0], d: 50.0 },
|
||||
collision::Plane { n: [0.0, 0.0, -1.0], d: 50.0 },
|
||||
],
|
||||
collision::aabb_brush([-50.0, -1.0, -50.0], [0.0, 0.0, 50.0]),
|
||||
])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn falls_under_gravity() {
|
||||
let mut p = Player::new([0.0, 5.0, 0.0]);
|
||||
p.step(&void(), 0.0, 0.0, false, 0.1);
|
||||
assert!(p.pos[1] < 5.0, "sollte fallen: y={}", p.pos[1]);
|
||||
assert!(p.vel[1] < 0.0, "vel.y sollte negativ sein: {}", p.vel[1]);
|
||||
assert!(!p.grounded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lands_and_rests_on_ground() {
|
||||
let w = floor();
|
||||
let mut p = Player::new([0.0, 0.5, 0.0]);
|
||||
p.vel[1] = -10.0;
|
||||
// Mehrere Schritte fallen + setzen lassen.
|
||||
for _ in 0..5 { p.step(&w, 0.0, 0.0, false, 0.1); }
|
||||
assert!(p.grounded, "sollte auf dem Boden stehen");
|
||||
assert!(p.pos[1] >= 0.0 && p.pos[1] < 0.05, "Füße ~ auf y=0: {}", p.pos[1]);
|
||||
assert!(p.vel[1].abs() < 1e-3, "vertikal in Ruhe: {}", p.vel[1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jump_only_when_grounded() {
|
||||
// Am Boden: Sprung hebt ab.
|
||||
let mut p = Player::new([0.0, 0.0, 0.0]);
|
||||
p.grounded = true;
|
||||
p.step(&void(), 0.0, 0.0, true, 0.016);
|
||||
assert!(p.vel[1] > 0.0, "Sprung sollte vel.y heben: {}", p.vel[1]);
|
||||
assert!(!p.grounded);
|
||||
|
||||
// In der Luft: kein zweiter Sprung — vel.y bleibt von Gravitation
|
||||
// bestimmt (negativ), nicht auf JUMP_SPEED gesetzt.
|
||||
let mut a = Player::new([0.0, 5.0, 0.0]);
|
||||
a.grounded = false;
|
||||
a.step(&void(), 0.0, 0.0, true, 0.016);
|
||||
assert!(a.vel[1] < 0.0, "kein Doppelsprung: {}", a.vel[1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walks_horizontally_along_yaw() {
|
||||
// yaw=0: vorwärts → -Z, strafe → +X; y bleibt am Boden (kein Drift).
|
||||
let w = floor();
|
||||
let mut p = Player::new([0.0, 0.0, 0.0]);
|
||||
p.grounded = true;
|
||||
p.step(&w, 1.0, 0.0, false, 0.1);
|
||||
assert!(p.pos[2] < 0.0, "vorwärts sollte -Z sein: {}", p.pos[2]);
|
||||
assert!(p.pos[0].abs() < 1e-6, "kein Seitversatz: {}", p.pos[0]);
|
||||
assert!(p.pos[1] < 0.05, "kein Y-Drift: {}", p.pos[1]);
|
||||
|
||||
let mut q = Player::new([0.0, 0.0, 0.0]);
|
||||
q.grounded = true;
|
||||
q.step(&w, 0.0, 1.0, false, 0.1);
|
||||
assert!(q.pos[0] > 0.0, "strafe rechts sollte +X sein: {}", q.pos[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looking_up_does_not_lift_movement() {
|
||||
// Pitch nach oben darf die Horizontalbewegung nicht kippen.
|
||||
let mut p = Player::new([0.0, 0.0, 0.0]);
|
||||
p.grounded = true;
|
||||
p.pitch = 1.0;
|
||||
p.step(&floor(), 1.0, 0.0, false, 0.1);
|
||||
assert!(p.pos[1] < 0.05, "Hochschauen darf nicht abheben: y={}", p.pos[1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yaw_quarter_turn_walks_minus_x() {
|
||||
// yaw=90° blickt -X (wie math::view) → vorwärts bewegt nach -X.
|
||||
let mut p = Player::new([0.0, 0.0, 0.0]);
|
||||
p.grounded = true;
|
||||
p.yaw = std::f32::consts::FRAC_PI_2;
|
||||
p.step(&floor(), 1.0, 0.0, false, 0.1);
|
||||
assert!(p.pos[0] < 0.0, "yaw=90° vorwärts sollte -X sein: {}", p.pos[0]);
|
||||
assert!(p.pos[2].abs() < 1e-6, "kein Z-Anteil: {}", p.pos[2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wall_blocks_horizontal_movement() {
|
||||
// Wand bei x=1 (Brush x∈[1,3]); Player läuft nach +X (yaw=-90°) dagegen.
|
||||
let w = collision::from_brushes(vec![
|
||||
collision::aabb_brush([1.0, -1.0, -50.0], [3.0, 5.0, 50.0]),
|
||||
]);
|
||||
let mut p = Player::new([0.0, 0.0, 0.0]);
|
||||
p.grounded = true;
|
||||
p.yaw = -std::f32::consts::FRAC_PI_2; // vorwärts → +X
|
||||
for _ in 0..10 { p.step(&w, 1.0, 0.0, false, 0.05); }
|
||||
// AABB-Halbbreite 0.3 → Füße stoppen vor x = 1 - 0.3 = 0.7.
|
||||
assert!(p.pos[0] < 0.72, "sollte an der Wand stoppen: x={}", p.pos[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ground_snap_keeps_grounded_over_step_down() {
|
||||
let w = step_down();
|
||||
let mut p = Player::new([-1.0, 0.6, 0.0]);
|
||||
for _ in 0..6 { p.step(&w, 0.0, 0.0, false, 0.1); } // auf obere Fläche fallen
|
||||
assert!(p.grounded && (p.pos[1] - 0.3).abs() < 0.05, "auf oberer Fläche: y={}", p.pos[1]);
|
||||
|
||||
p.yaw = -std::f32::consts::FRAC_PI_2; // vorwärts → +X über die Naht
|
||||
for _ in 0..8 {
|
||||
p.step(&w, 1.0, 0.0, false, 0.1);
|
||||
assert!(p.grounded, "Snap soll über die Stufe am Boden halten: y={}", p.pos[1]);
|
||||
}
|
||||
assert!(p.pos[0] > 0.3, "sollte die Naht überquert haben: x={}", p.pos[0]);
|
||||
assert!(p.pos[1] < 0.05, "auf der unteren Fläche angekommen: y={}", p.pos[1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ground_snap_follows_downhill_slope() {
|
||||
let w = ramp();
|
||||
let mut p = Player::new([1.7, 1.3, 0.0]);
|
||||
for _ in 0..8 { p.step(&w, 0.0, 0.0, false, 0.1); } // auf die Slope setzen
|
||||
assert!(p.grounded, "auf der Slope gelandet: y={}", p.pos[1]);
|
||||
|
||||
p.yaw = std::f32::consts::FRAC_PI_2; // vorwärts → -X (abwärts)
|
||||
let y0 = p.pos[1];
|
||||
for _ in 0..3 {
|
||||
p.step(&w, 1.0, 0.0, false, 0.1);
|
||||
assert!(p.grounded, "Snap soll auf der Slope am Boden halten: y={}", p.pos[1]);
|
||||
}
|
||||
assert!(p.pos[1] < y0 - 0.1, "sollte die Slope hinab (y sinkt): {y0} -> {}", p.pos[1]);
|
||||
assert!(p.pos[0] < 1.7, "bewegt sich abwärts (-X): x={}", p.pos[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_snap_off_tall_ledge() {
|
||||
let w = tall_ledge();
|
||||
let mut p = Player::new([-1.0, 0.3, 0.0]);
|
||||
for _ in 0..5 { p.step(&w, 0.0, 0.0, false, 0.1); } // auf obere Fläche setzen
|
||||
p.yaw = -std::f32::consts::FRAC_PI_2; // über die hohe Kante nach +X
|
||||
for _ in 0..6 { p.step(&w, 1.0, 0.0, false, 0.1); }
|
||||
assert!(!p.grounded, "über die hohe Kante (> SNAP_DIST) soll er fallen");
|
||||
assert!(p.pos[1] < 0.0, "fällt ins Tiefere: y={}", p.pos[1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jump_overrides_snap() {
|
||||
let w = floor();
|
||||
let mut p = Player::new([0.0, 0.5, 0.0]); // von oben einpendeln (ds>0)
|
||||
for _ in 0..5 { p.step(&w, 0.0, 0.0, false, 0.1); } // am Boden setzen
|
||||
assert!(p.grounded);
|
||||
p.step(&w, 0.0, 0.0, true, 0.1); // springen
|
||||
assert!(!p.grounded, "Sprung darf nicht vom Snap annulliert werden");
|
||||
assert!(p.vel[1] > 0.0, "steigt nach dem Sprung: {}", p.vel[1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn step_up_over_curb() {
|
||||
// 0.4 hohe Kante (< STEP_HEIGHT) vor einer Fläche: muss erstiegen werden.
|
||||
let w = collision::from_brushes(vec![
|
||||
collision::aabb_brush([-50.0, -1.0, -50.0], [0.0, 0.0, 50.0]), // Boden y=0, x≤0
|
||||
collision::aabb_brush([0.0, -1.0, -50.0], [50.0, 0.4, 50.0]), // Stufe y=0.4, x≥0
|
||||
]);
|
||||
let mut p = Player::new([-1.0, 0.3, 0.0]);
|
||||
for _ in 0..30 { p.step(&w, 0.0, 0.0, false, 1.0 / 60.0); }
|
||||
p.yaw = -std::f32::consts::FRAC_PI_2; // vorwärts → +X gegen die Stufe
|
||||
for _ in 0..120 { p.step(&w, 1.0, 0.0, false, 1.0 / 60.0); }
|
||||
assert!(p.pos[0] > 0.3, "sollte die Stufe erstiegen haben: x={}", p.pos[0]);
|
||||
assert!((p.pos[1] - 0.4).abs() < 0.05, "steht auf der Stufe: y={}", p.pos[1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tall_step_blocks() {
|
||||
// 1.0 hohe Wand (> STEP_HEIGHT): kein Erklimmen.
|
||||
let w = collision::from_brushes(vec![
|
||||
collision::aabb_brush([-50.0, -1.0, -50.0], [0.0, 0.0, 50.0]),
|
||||
collision::aabb_brush([0.0, -1.0, -50.0], [50.0, 1.0, 50.0]),
|
||||
]);
|
||||
let mut p = Player::new([-1.0, 0.3, 0.0]);
|
||||
for _ in 0..30 { p.step(&w, 0.0, 0.0, false, 1.0 / 60.0); }
|
||||
p.yaw = -std::f32::consts::FRAC_PI_2;
|
||||
for _ in 0..120 { p.step(&w, 1.0, 0.0, false, 1.0 / 60.0); }
|
||||
assert!(p.pos[0] < -0.25, "hohe Wand muss blocken: x={}", p.pos[0]);
|
||||
}
|
||||
|
||||
/// Slope (von (0,1) hinab auf (2,0)) zwischen oberer Fläche (y=1, x≤0) und
|
||||
/// unterer (y=0, x≥2). Genau der reale Naht-Fall — bei 60 fps getestet.
|
||||
fn slope_between_floors() -> CollisionWorld {
|
||||
let s = 5.0f32.sqrt();
|
||||
collision::from_brushes(vec![
|
||||
collision::aabb_brush([-50.0, -1.0, -50.0], [0.0, 1.0, 50.0]),
|
||||
vec![
|
||||
collision::Plane { n: [1.0 / s, 2.0 / s, 0.0], d: 2.0 / s }, // Slope-Oberfläche
|
||||
collision::Plane { n: [0.0, -1.0, 0.0], d: 0.0 },
|
||||
collision::Plane { n: [-1.0, 0.0, 0.0], d: 0.0 },
|
||||
collision::Plane { n: [1.0, 0.0, 0.0], d: 2.0 },
|
||||
collision::Plane { n: [0.0, 0.0, 1.0], d: 50.0 },
|
||||
collision::Plane { n: [0.0, 0.0, -1.0], d: 50.0 },
|
||||
],
|
||||
collision::aabb_brush([2.0, -1.0, -50.0], [50.0, 0.0, 50.0]),
|
||||
])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walks_down_slope_across_seams_at_60fps() {
|
||||
let w = slope_between_floors();
|
||||
let mut p = Player::new([-1.0, 1.3, 0.0]);
|
||||
for _ in 0..60 { p.step(&w, 0.0, 0.0, false, 1.0 / 60.0); } // obere Fläche
|
||||
assert!(p.grounded && (p.pos[1] - 1.0).abs() < 0.05, "auf oberer Fläche: y={}", p.pos[1]);
|
||||
|
||||
p.yaw = -std::f32::consts::FRAC_PI_2; // +X, abwärts über beide Nähte
|
||||
for i in 0..180 {
|
||||
p.step(&w, 1.0, 0.0, false, 1.0 / 60.0);
|
||||
assert!(p.grounded, "darf an der Naht nicht hängenbleiben (i={i}, x={}, y={})", p.pos[0], p.pos[1]);
|
||||
}
|
||||
assert!(p.pos[0] > 2.0, "sollte auf die untere Fläche gelangt sein: x={}", p.pos[0]);
|
||||
assert!((p.pos[1]).abs() < 0.05, "auf der unteren Fläche: y={}", p.pos[1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walks_up_slope_across_seams_at_60fps() {
|
||||
let w = slope_between_floors();
|
||||
let mut p = Player::new([4.0, 0.3, 0.0]);
|
||||
for _ in 0..60 { p.step(&w, 0.0, 0.0, false, 1.0 / 60.0); } // untere Fläche
|
||||
assert!(p.grounded && p.pos[1].abs() < 0.05, "auf unterer Fläche: y={}", p.pos[1]);
|
||||
|
||||
p.yaw = std::f32::consts::FRAC_PI_2; // -X, aufwärts über beide Nähte
|
||||
for i in 0..180 {
|
||||
p.step(&w, 1.0, 0.0, false, 1.0 / 60.0);
|
||||
assert!(p.grounded, "darf an der Naht nicht hängenbleiben (i={i}, x={}, y={})", p.pos[0], p.pos[1]);
|
||||
}
|
||||
assert!(p.pos[0] < 0.0, "sollte auf die obere Fläche gelangt sein: x={}", p.pos[0]);
|
||||
assert!((p.pos[1] - 1.0).abs() < 0.05, "auf der oberen Fläche: y={}", p.pos[1]);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
//! `clear <name>` KV-Eintrag entfernen
|
||||
//! `hide_object <name>` → Action::HideObject (deferred)
|
||||
//! `play_sound <file>` → Action::PlaySound (deferred)
|
||||
//! `mode <play|free|menu>` → Action::SetMode (deferred): Anzeige-/
|
||||
//! Eingabemodus wechseln
|
||||
//!
|
||||
//! Parameter-Substitution: vor dem Parsen ersetzt `execute` `$self` in den
|
||||
//! Action-Args durch `ctx.instance_name`. Damit kann eine generische Action
|
||||
@@ -30,7 +32,7 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
use crate::engine::game::{Action, ActionCtx, Signals};
|
||||
use crate::engine::game::{Action, ActionCtx, ModeTarget, Signals};
|
||||
use crate::engine::kv;
|
||||
use crate::engine::story_ctrl;
|
||||
|
||||
@@ -82,6 +84,9 @@ fn execute(cmd: &str, ctx: &mut ActionCtx) {
|
||||
"clear" => kv::apply_clear(args, ctx.kv),
|
||||
"hide_object" => ctx.actions.push(Action::HideObject(args.to_string())),
|
||||
"play_sound" => ctx.actions.push(Action::PlaySound(args.to_string())),
|
||||
"mode" => if let Some(t) = ModeTarget::parse(args) {
|
||||
ctx.actions.push(Action::SetMode(t));
|
||||
},
|
||||
_ => { /* Unbekannter Verb/Tag → still ignorieren */ }
|
||||
}
|
||||
}
|
||||
@@ -176,6 +181,20 @@ mod tests {
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mode_verb_queues_setmode_action() {
|
||||
let mut game = Game::new(Signals::new());
|
||||
let mut ctx = game.action_ctx(None);
|
||||
dispatch("mode free", &mut ctx);
|
||||
assert_eq!(game.actions, vec![Action::SetMode(ModeTarget::Free)]);
|
||||
|
||||
// Unbekanntes Ziel → kein Action (stilles No-Op).
|
||||
game.actions.clear();
|
||||
let mut ctx = game.action_ctx(None);
|
||||
dispatch("mode wat", &mut ctx);
|
||||
assert!(game.actions.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_signal_falls_through_to_builtin() {
|
||||
let mut game = Game::new(Signals::new());
|
||||
|
||||
@@ -23,11 +23,9 @@
|
||||
//! Quake-System gerechnet (so sind Offsets/Scale definiert), die Position
|
||||
//! danach umgerechnet: `(qx, qy, qz) → (qx, qz, -qy)`, mal `MAP_SCALE`.
|
||||
|
||||
use crate::engine::map::{Face, Map};
|
||||
use crate::engine::map::{self, Face, Map};
|
||||
use crate::render::scene::{Batch, Mesh, Vertex};
|
||||
|
||||
/// Quake-Einheiten pro Engine-Einheit (32 ≈ klassische „1 Meter"-Annahme).
|
||||
const MAP_SCALE: f32 = 1.0 / 32.0;
|
||||
/// Punkt liegt „auf" einer Ebene / „innerhalb" eines Halbraums (Quake-Units).
|
||||
const ON_EPS: f32 = 1e-2;
|
||||
|
||||
@@ -207,7 +205,7 @@ fn emit_face(
|
||||
// UV in Texeln, dann auf 0..1 normalisiert.
|
||||
let u = (dot(*p, su) + fp.face.offset[0]) / tw as f32;
|
||||
let v = (dot(*p, sv) + fp.face.offset[1]) / th as f32;
|
||||
verts.push(Vertex { pos: to_engine(*p), uv: [u, v] });
|
||||
verts.push(Vertex { pos: map::to_engine(*p), uv: [u, v] });
|
||||
}
|
||||
for t in 1..(fp.poly.len() as u32 - 1) {
|
||||
per_tex[ti].extend([base, base + t, base + t + 1]);
|
||||
@@ -273,10 +271,6 @@ fn base_axes(n: [f32; 3]) -> ([f32; 3], [f32; 3]) {
|
||||
(BA[bi][1], BA[bi][2])
|
||||
}
|
||||
|
||||
fn to_engine(p: [f32; 3]) -> [f32; 3] {
|
||||
[p[0] * MAP_SCALE, p[2] * MAP_SCALE, -p[1] * MAP_SCALE]
|
||||
}
|
||||
|
||||
// --- kleine Vektor-Helfer auf [f32; 3] ---------------------------------------
|
||||
|
||||
fn sub(a: [f32; 3], b: [f32; 3]) -> [f32; 3] { [a[0] - b[0], a[1] - b[1], a[2] - b[2]] }
|
||||
|
||||
@@ -40,6 +40,8 @@ pub(crate) struct Font {
|
||||
/// Die im Projekt geladenen Fonts, gebaut in render::run und an `ui::layout`
|
||||
/// gereicht.
|
||||
pub(crate) struct Fonts {
|
||||
/// Größerer Font, für spätere Überschriften/HUD bereitgehalten.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) ega: Font,
|
||||
pub(crate) cga: Font,
|
||||
}
|
||||
|
||||
@@ -33,17 +33,25 @@ use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
|
||||
use winit::keyboard::{KeyCode, PhysicalKey};
|
||||
use winit::window::{CursorGrabMode, Window, WindowId};
|
||||
|
||||
use crate::engine::player;
|
||||
use crate::engine::tga::Image;
|
||||
use crate::engine::{assets, map, tga};
|
||||
use crate::session::{Mode, Session};
|
||||
use crate::session::{FrameInput, Mode, Session};
|
||||
use camera::Camera;
|
||||
use gpu::Gpu;
|
||||
use math::Mat4;
|
||||
use scene::Mesh;
|
||||
|
||||
/// Radiant pro Maus-Pixel.
|
||||
const MOUSE_SENS: f32 = 0.0025;
|
||||
/// Welt-Einheiten pro Sekunde.
|
||||
const MOVE_SPEED: f32 = 4.0;
|
||||
/// Zeitkonstante (s) des Eye-Height-Smoothings am Boden: die Kamera gleitet
|
||||
/// vertikal sanft auf Augenhöhe nach, statt Step-up-/Slope-Sprüngen sofort zu
|
||||
/// folgen (wie Source/HL2). Größer = weicher, aber träger.
|
||||
const EYE_SMOOTH_TAU: f32 = 0.06;
|
||||
/// Maximaler vertikaler Nachlauf der Kamera hinter den Füßen (units).
|
||||
const EYE_MAX_LAG: f32 = 0.6;
|
||||
|
||||
pub fn run(mut session: Session) {
|
||||
// Init-Signal feuern, bevor das Fenster steht (kann bereits einen
|
||||
@@ -62,6 +70,10 @@ pub fn run(mut session: Session) {
|
||||
let mesh = brush::build(&world, &tex_names, &dims);
|
||||
report_map(&world, &tex_names, &mesh);
|
||||
|
||||
// Sim-Welt (Collision-Brushes + Spawn) in die Session einspielen — die
|
||||
// Simulation gehört der Session, nicht dem Render-Frontend.
|
||||
session.load_world(&world);
|
||||
|
||||
// UI-Texturen, Reihenfolge = die Index-Konstanten in ui (WHITE, FONT_EGA,
|
||||
// FONT_CGA, CURSORS, ORN). Decode (CPU) bleibt in run(); Fonts, Cursor und
|
||||
// Ornament sind weiß-auf-schwarz und bekommen ihre Alpha-Maske aus der
|
||||
@@ -134,6 +146,8 @@ struct Input {
|
||||
right: bool,
|
||||
up: bool,
|
||||
down: bool,
|
||||
/// Sprung-Flanke: bei Space-Druck gesetzt, nach `player::step` verbraucht.
|
||||
jump: bool,
|
||||
mouse_dx: f32,
|
||||
mouse_dy: f32,
|
||||
/// Cursor gefangen → Maus-Look aktiv.
|
||||
@@ -145,11 +159,16 @@ struct App {
|
||||
gpu: Option<Gpu>,
|
||||
session: Session,
|
||||
rx: Receiver<String>,
|
||||
/// Noclip-Debug-Flycam (reine Frontend-Sicht, kein Sim-Zustand).
|
||||
camera: Camera,
|
||||
/// Geglättete Kamera-Augenhöhe (Welt-Y) — siehe Eye-Height-Smoothing.
|
||||
eye_y: f32,
|
||||
input: Input,
|
||||
last: Option<Instant>,
|
||||
/// Letzte Mausposition in Fenster-Pixeln (für den UI-Cursor).
|
||||
cursor_win: [f32; 2],
|
||||
/// Linksklick steht aus, wird im nächsten redraw gegen das UI aufgelöst.
|
||||
pending_click: bool,
|
||||
/// CPU-seitige Welt-Geometrie + Texturen, in `resumed` einmal auf die
|
||||
/// GPU geladen.
|
||||
mesh: Mesh,
|
||||
@@ -167,6 +186,8 @@ impl App {
|
||||
ui_textures: Vec<Image>,
|
||||
fonts: font::Fonts,
|
||||
) -> Self {
|
||||
// Kamera-Augenhöhe initial auf den Spawn des Players (gehört der Session).
|
||||
let eye_y = session.player.pos[1] + player::EYE_HEIGHT;
|
||||
Self {
|
||||
window: None,
|
||||
gpu: None,
|
||||
@@ -174,9 +195,11 @@ impl App {
|
||||
rx,
|
||||
// Etwas zurück und erhöht, Blick Richtung Welt-Ursprung.
|
||||
camera: Camera::new([0.0, 1.0, 4.0]),
|
||||
eye_y,
|
||||
input: Input::default(),
|
||||
last: None,
|
||||
cursor_win: [0.0, 0.0],
|
||||
pending_click: false,
|
||||
mesh,
|
||||
images,
|
||||
ui_textures,
|
||||
@@ -255,9 +278,10 @@ impl ApplicationHandler for App {
|
||||
self.cursor_win = [position.x as f32, position.y as f32];
|
||||
}
|
||||
WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left, .. } => {
|
||||
// Nur im freien Modus greift der Klick die Maus (Flycam). Im
|
||||
// UI-/Dialog-Modus bleibt die Maus frei (für Klicks aufs UI).
|
||||
if matches!(self.session.mode, Mode::Free) { self.set_grab(true); }
|
||||
// Klick nur vormerken; der nächste redraw entscheidet (mit der
|
||||
// frisch berechneten Hover-Aktion) zwischen UI-Klick und
|
||||
// Flycam-Greifen.
|
||||
self.pending_click = true;
|
||||
}
|
||||
WindowEvent::KeyboardInput { event: key, .. } => self.on_key(event_loop, key),
|
||||
WindowEvent::Resized(size) => {
|
||||
@@ -265,7 +289,7 @@ impl ApplicationHandler for App {
|
||||
gpu.resize(size.width, size.height);
|
||||
}
|
||||
}
|
||||
WindowEvent::RedrawRequested => self.redraw(),
|
||||
WindowEvent::RedrawRequested => self.redraw(event_loop),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -276,9 +300,16 @@ impl App {
|
||||
let PhysicalKey::Code(code) = key.physical_key else { return; };
|
||||
let down = key.state == ElementState::Pressed;
|
||||
|
||||
// Escape: erst den Cursor freigeben, sonst beenden.
|
||||
// Escape togglet das Menü über denselben exec-Trichter wie Konsole
|
||||
// und Klick. Der Lock-Zustand folgt danach automatisch dem Modus
|
||||
// (siehe redraw). Im Dialog tut Escape nichts (Dialog endet über
|
||||
// seine Choices).
|
||||
if code == KeyCode::Escape && down {
|
||||
if self.input.grabbed { self.set_grab(false); } else { event_loop.exit(); }
|
||||
if !matches!(self.session.mode, Mode::Dialog(_)) {
|
||||
let r = self.session.exec("menu");
|
||||
for out in r.output { println!("{out}"); }
|
||||
if r.quit { event_loop.exit(); }
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -287,35 +318,79 @@ impl App {
|
||||
KeyCode::KeyS => self.input.back = down,
|
||||
KeyCode::KeyA => self.input.left = down,
|
||||
KeyCode::KeyD => self.input.right = down,
|
||||
KeyCode::Space => self.input.up = down,
|
||||
// Space: in der Flycam „hoch" (gehalten), im Spiel Sprung (Flanke,
|
||||
// nicht Auto-Repeat). Beide Deutungen aus derselben Taste.
|
||||
KeyCode::Space => {
|
||||
self.input.up = down;
|
||||
if down && !key.repeat { self.input.jump = true; }
|
||||
}
|
||||
KeyCode::ShiftLeft => self.input.down = down,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn redraw(&mut self) {
|
||||
fn redraw(&mut self, event_loop: &ActiveEventLoop) {
|
||||
// dt messen; im Dialog Kamera und Sim anhalten → Welt pausiert,
|
||||
// während die Konsole den Dialog treibt.
|
||||
let now = Instant::now();
|
||||
let dt = self.last.replace(now).map_or(0.0, |prev| (now - prev).as_secs_f32());
|
||||
|
||||
if matches!(self.session.mode, Mode::Free) {
|
||||
// Lock-Zustand folgt dem Modus: Spiel (Play) und Flycam (Free) fangen
|
||||
// die Maus (relativer Blick); Menü/Dialog geben sie frei fürs UI. So
|
||||
// gibt es keine parallele Lock-Logik.
|
||||
let fly = matches!(self.session.mode, Mode::Free);
|
||||
let play = matches!(self.session.mode, Mode::FirstPerson);
|
||||
let grab = fly || play;
|
||||
if grab != self.input.grabbed { self.set_grab(grab); }
|
||||
|
||||
if fly {
|
||||
// Flycam ist Frontend-Sicht: hier (nicht in der Session) bewegt.
|
||||
self.camera.apply_mouse(self.input.mouse_dx, self.input.mouse_dy, MOUSE_SENS);
|
||||
let fwd = axis(self.input.fwd, self.input.back);
|
||||
let right = axis(self.input.right, self.input.left);
|
||||
let up = axis(self.input.up, self.input.down);
|
||||
self.camera.translate(fwd, right, up, MOVE_SPEED * dt);
|
||||
} else if self.input.grabbed {
|
||||
// Nicht-Free (Dialog/Menü) → Maus freigeben, damit das UI klickbar
|
||||
// ist und der Maus-Cursor statt des HUD-Pointers erscheint.
|
||||
self.set_grab(false);
|
||||
} else if play {
|
||||
// Eingaben einsammeln und die Simulation in der Session treiben.
|
||||
let input = FrameInput {
|
||||
fwd: axis(self.input.fwd, self.input.back),
|
||||
right: axis(self.input.right, self.input.left),
|
||||
jump: self.input.jump,
|
||||
look_dx: self.input.mouse_dx,
|
||||
look_dy: self.input.mouse_dy,
|
||||
};
|
||||
self.session.tick(&input, dt);
|
||||
}
|
||||
// Delta immer verwerfen, auch im Dialog — sonst springt der Blick
|
||||
// beim Fortsetzen um die aufgestaute Bewegung.
|
||||
// Deltas/Flanke immer verwerfen, auch im Dialog — sonst springt der
|
||||
// Blick beim Fortsetzen um die aufgestaute Bewegung.
|
||||
self.input.mouse_dx = 0.0;
|
||||
self.input.mouse_dy = 0.0;
|
||||
self.input.jump = false;
|
||||
|
||||
let view = self.camera.view();
|
||||
// Eye-Height-Smoothing: die Kamera-Augenhöhe gleitet am Boden sanft auf
|
||||
// den Zielwert nach, statt Step-up-/Slope-Kanten-Sprüngen sofort zu
|
||||
// folgen (versteckt den AABB-auf-Slope-„Pop" und macht Treppen weich).
|
||||
// In der Luft (Sprung/Fall) und außerhalb des Spiels exakt — da soll
|
||||
// die Kamera reagieren bzw. mit der Flycam synchron bleiben.
|
||||
let target_eye = self.session.player.pos[1] + player::EYE_HEIGHT;
|
||||
if play && self.session.player.grounded {
|
||||
let a = 1.0 - (-dt / EYE_SMOOTH_TAU).exp();
|
||||
self.eye_y += (target_eye - self.eye_y) * a;
|
||||
self.eye_y = self.eye_y.clamp(target_eye - EYE_MAX_LAG, target_eye + EYE_MAX_LAG);
|
||||
} else {
|
||||
self.eye_y = target_eye;
|
||||
}
|
||||
|
||||
// View: nur die Noclip-Flycam (Free) rendert aus `self.camera`. Alles
|
||||
// andere — Spiel, Menü, Dialog — sieht durch die Augen des Players
|
||||
// (geglättete Augenhöhe); Menü/Dialog frieren die Sim nur ein, die
|
||||
// Sicht bleibt die First-Person-Sicht.
|
||||
let view = if fly {
|
||||
self.camera.view()
|
||||
} else {
|
||||
let p = self.session.player.pos;
|
||||
Mat4::view([p[0], self.eye_y, p[2]], self.session.player.yaw, self.session.player.pitch)
|
||||
};
|
||||
// Mausposition über die Letterbox in interne Pixel mappen.
|
||||
let cursor = ui::Cursor {
|
||||
pos: self.gpu.as_ref()
|
||||
@@ -323,11 +398,25 @@ impl App {
|
||||
.unwrap_or([0.0, 0.0]),
|
||||
grabbed: self.input.grabbed,
|
||||
};
|
||||
// Overlay state-driven bauen (Stage 3: Demo-Panel + Cursor).
|
||||
let overlay = ui::layout(
|
||||
[gpu::INTERNAL_W as f32, gpu::INTERNAL_H as f32], &self.fonts, &cursor,
|
||||
// Overlay state-driven bauen (verzweigt nach session.mode); die
|
||||
// Hover-Aktion kommt aus der Maus-Position.
|
||||
let screen = ui::layout(
|
||||
[gpu::INTERNAL_W as f32, gpu::INTERNAL_H as f32], &self.fonts, &self.session, &cursor,
|
||||
);
|
||||
if let Some(gpu) = &mut self.gpu { gpu.frame(&view, &overlay); }
|
||||
|
||||
// Vorgemerkten Klick auflösen: über einem Klickziel → dessen Aktion
|
||||
// durch denselben exec-Trichter wie die Konsole. Klick in der freien
|
||||
// Welt tut (noch) nichts — hier käme später der `use`-Raycast.
|
||||
if self.pending_click {
|
||||
self.pending_click = false;
|
||||
if let Some(action) = screen.hover_action.clone() {
|
||||
let r = self.session.exec(&action);
|
||||
for out in r.output { println!("{out}"); }
|
||||
if r.quit { event_loop.exit(); }
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(gpu) = &mut self.gpu { gpu.frame(&view, &screen.ui); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -136,13 +136,18 @@ impl ScenePass {
|
||||
}],
|
||||
});
|
||||
|
||||
// Nearest-Sampler: harte Texel, kein Filtering — PS1.
|
||||
// mag=Nearest hält nah dran die harten Texel (der knackige Look);
|
||||
// min/mipmap=Linear glätten nur die Verkleinerung in der Ferne und
|
||||
// blenden weich zwischen den Mip-Ebenen (kein Mip-Popping). Killt das
|
||||
// Texel-Flimmern hochauflösender Texturen auf der kleinen internen
|
||||
// Auflösung. Mipmaps werden in upload_texture per Box-Filter erzeugt.
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("scene nearest"),
|
||||
label: Some("scene"),
|
||||
address_mode_u: wgpu::AddressMode::Repeat,
|
||||
address_mode_v: wgpu::AddressMode::Repeat,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::MipmapFilterMode::Linear,
|
||||
..Default::default()
|
||||
});
|
||||
let tex_layout = pipeline.get_bind_group_layout(1);
|
||||
@@ -176,7 +181,8 @@ impl ScenePass {
|
||||
}
|
||||
|
||||
/// Ein RGBA8-`Image` als GPU-Textur hochladen und die zugehörige
|
||||
/// Bind-Group (Textur + Sampler, group 1) bauen.
|
||||
/// Bind-Group (Textur + Sampler, group 1) bauen. Erzeugt die volle
|
||||
/// Mipchain (bis 1×1) per Box-Filter auf der CPU und lädt jede Ebene hoch.
|
||||
fn upload_texture(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
@@ -184,15 +190,16 @@ fn upload_texture(
|
||||
sampler: &wgpu::Sampler,
|
||||
img: &Image,
|
||||
) -> wgpu::BindGroup {
|
||||
let size = wgpu::Extent3d {
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
// floor(log2(max(w,h))) + 1 = volle Kette bis zur 1×1-Ebene.
|
||||
let mip_level_count = 32 - img.width.max(img.height).leading_zeros();
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("scene texture"),
|
||||
size,
|
||||
mip_level_count: 1,
|
||||
size: wgpu::Extent3d {
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
// Nicht-sRGB: die Quantisierung im Shader erwartet rohe Werte.
|
||||
@@ -200,21 +207,32 @@ fn upload_texture(
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
});
|
||||
queue.write_texture(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: &texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
&img.rgba,
|
||||
wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(img.width * 4),
|
||||
rows_per_image: Some(img.height),
|
||||
},
|
||||
size,
|
||||
);
|
||||
|
||||
// Ebene 0 ist das Originalbild; jede weitere wird aus der vorherigen
|
||||
// halbiert. `level` trägt die aktuell hochzuladenden Pixel.
|
||||
let mut level = img.rgba.clone();
|
||||
let (mut w, mut h) = (img.width, img.height);
|
||||
for mip in 0..mip_level_count {
|
||||
queue.write_texture(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: &texture,
|
||||
mip_level: mip,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
&level,
|
||||
wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(w * 4),
|
||||
rows_per_image: Some(h),
|
||||
},
|
||||
wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
|
||||
);
|
||||
if mip + 1 < mip_level_count {
|
||||
(level, w, h) = downsample(&level, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
let view = texture.create_view(&Default::default());
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("scene texture"),
|
||||
@@ -225,3 +243,29 @@ fn upload_texture(
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
/// Eine RGBA8-Mip-Ebene per 2×2-Box-Filter auf die nächstkleinere halbieren.
|
||||
/// Gibt die neuen Pixel samt Maßen zurück. Ungerade Maße werden via `(d+1)/2`
|
||||
/// aufgerundet (sonst ginge die letzte Spalte/Zeile verloren); die fehlende
|
||||
/// Quell-Spalte/-Zeile wird auf den Rand geklemmt, statt den Mittelwert zu
|
||||
/// verfälschen. Mittelung im (gamma-kodierten) Speicherraum — für den
|
||||
/// stilisierten Look unkritisch.
|
||||
fn downsample(src: &[u8], w: u32, h: u32) -> (Vec<u8>, u32, u32) {
|
||||
let (nw, nh) = ((w + 1) / 2, (h + 1) / 2);
|
||||
let mut dst = vec![0u8; (nw * nh * 4) as usize];
|
||||
for y in 0..nh {
|
||||
for x in 0..nw {
|
||||
let (x0, y0) = (2 * x, 2 * y);
|
||||
let x1 = (x0 + 1).min(w - 1);
|
||||
let y1 = (y0 + 1).min(h - 1);
|
||||
let at = |px: u32, py: u32| ((py * w + px) * 4) as usize;
|
||||
let samples = [at(x0, y0), at(x1, y0), at(x0, y1), at(x1, y1)];
|
||||
let di = ((y * nw + x) * 4) as usize;
|
||||
for c in 0..4 {
|
||||
let sum: u32 = samples.iter().map(|&s| src[s + c] as u32).sum();
|
||||
dst[di + c] = ((sum + 2) / 4) as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
(dst, nw, nh)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
// Szenen-Pipeline: die PS1-Eigenheiten aus dem Renderer-Plan, authentisch
|
||||
// gerechnet statt nachgestellt:
|
||||
// - Pixel-Snap im Vertex-Shader → Vertex-Jitter
|
||||
// - @interpolate(linear) → affine (nicht perspektivkorrigierte) UVs
|
||||
// - RGB555-Quantisierung + 4×4-Bayer-Dither im Fragment-Shader
|
||||
// - 1-Bit-Alpha-Test (Cutouts), kein Blending
|
||||
//
|
||||
// UVs werden perspektivkorrekt interpoliert (@interpolate(perspective)). Das
|
||||
// klassische PS1-Textur-Wobbeln (affin, @interpolate(linear)) ist mit einer
|
||||
// frei beweglichen Kamera und großen Faces zu störend — den Look tragen
|
||||
// Pixel-Snap und Dither.
|
||||
|
||||
struct Uniforms {
|
||||
mvp: mat4x4f,
|
||||
@@ -18,8 +22,8 @@ struct Uniforms {
|
||||
|
||||
struct VsOut {
|
||||
@builtin(position) pos: vec4f,
|
||||
// linear = affin interpoliert → das Textur-Wobbeln der PS1.
|
||||
@location(0) @interpolate(linear) uv: vec2f,
|
||||
// perspektivkorrekt interpoliert (WGSL-Default) — kein Affin-Wobbeln.
|
||||
@location(0) @interpolate(perspective) uv: vec2f,
|
||||
};
|
||||
|
||||
@vertex
|
||||
@@ -49,9 +53,15 @@ fn bayer4(px: vec2u) -> f32 {
|
||||
return f32(m[(px.y % 4u) * 4u + (px.x % 4u)]);
|
||||
}
|
||||
|
||||
// LOD-Bias für die Mip-Auswahl: negativ = schärfer (greift einen höher
|
||||
// aufgelösten Mip, als die UV-Ableitungen verlangen), positiv = weicher.
|
||||
// 0 = neutral. Bei -1.0 verdoppelt sich praktisch die Texelrate (mehr
|
||||
// Schärfe, etwas mehr Flimmern); -0.5 ist ein sanfter Mittelweg.
|
||||
const MIP_BIAS: f32 = -1.0;
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VsOut) -> @location(0) vec4f {
|
||||
let texel = textureSample(tex, smp, in.uv);
|
||||
let texel = textureSampleBias(tex, smp, in.uv, MIP_BIAS);
|
||||
if texel.a < 0.5 { discard; } // 1-Bit-Alpha (Cutouts), noch ungenutzt
|
||||
|
||||
// RGB555: 31 Stufen pro Kanal. Bayer-Schwelle vor dem Abrunden →
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
use crate::engine::tga::Image;
|
||||
use crate::render::font::{self, Font, Fonts};
|
||||
use crate::render::sprite::{SpriteBatch, SpriteVertex};
|
||||
use crate::session::{Dialog, Mode, Session};
|
||||
|
||||
/// Feste Textur-Indizes im Sprite-Pass — Reihenfolge = `ui_textures` in
|
||||
/// render::run. `WHITE` ist ein 1×1-Weiß-Texel für Solid-Fills (Farbe kommt
|
||||
@@ -26,11 +27,11 @@ pub(crate) const CURSORS: usize = 3;
|
||||
pub(crate) const ORN: usize = 4;
|
||||
|
||||
/// Cursor-Indizes im 2×2-Raster (Pivot mittig je 16×16-Zelle).
|
||||
pub(crate) const CUR_MOUSE: usize = 0; // Maus, normal
|
||||
pub(crate) const CUR_HUD: usize = 2; // First-Person-Pointer, normal
|
||||
// Die „interagierbar"-Varianten (1, 3) kommen mit dem Hit-Testing in Stage 4.
|
||||
#[allow(dead_code)] pub(crate) const CUR_MOUSE_INTERACT: usize = 1;
|
||||
#[allow(dead_code)] pub(crate) const CUR_HUD_INTERACT: usize = 3;
|
||||
pub(crate) const CUR_MOUSE: usize = 0; // Maus, normal
|
||||
pub(crate) const CUR_MOUSE_INTERACT: usize = 1; // Maus, über Klickziel
|
||||
pub(crate) const CUR_HUD: usize = 2; // First-Person-Pointer, normal
|
||||
// Die interagierbare HUD-Variante (3) kommt mit dem Welt-Raycast.
|
||||
#[allow(dead_code)] pub(crate) const CUR_HUD_INTERACT: usize = 3;
|
||||
|
||||
const CURSOR_PX: f32 = 16.0;
|
||||
const ORN_CORNER: f32 = 8.0; // Eckgröße im Ornament-Atlas (und im Panel)
|
||||
@@ -180,27 +181,159 @@ fn cursor_uv(idx: usize) -> [f32; 4] {
|
||||
[x0 / 32.0, y0 / 32.0, (x0 + CURSOR_PX) / 32.0, (y0 + CURSOR_PX) / 32.0]
|
||||
}
|
||||
|
||||
/// Das Overlay für den aktuellen Frame bauen.
|
||||
//
|
||||
// Stage 3: Demo-Panel mit Ornamentrahmen + Text, plus der Cursor. Ab Stage 4
|
||||
// verzweigt das hier nach `session.mode` (Dialog-Panel, Menü, HUD).
|
||||
pub(crate) fn layout(internal: [f32; 2], fonts: &Fonts, cur: &Cursor) -> Ui {
|
||||
/// Ergebnis eines Frames: die Overlay-Geometrie und — falls der Cursor über
|
||||
/// einem Klickziel steht — dessen Aktion (Eingabe-String für `Session::exec`).
|
||||
pub(crate) struct Screen {
|
||||
pub(crate) ui: Ui,
|
||||
pub(crate) hover_action: Option<String>,
|
||||
}
|
||||
|
||||
/// Punkt in Rechteck (x, y, w, h)?
|
||||
fn in_rect(p: [f32; 2], r: [f32; 4]) -> bool {
|
||||
p[0] >= r[0] && p[0] < r[0] + r[2] && p[1] >= r[1] && p[1] < r[1] + r[3]
|
||||
}
|
||||
|
||||
/// Ein klickbarer Button: Fläche + zentriertes Label. Steht der Cursor
|
||||
/// darüber, wird er hervorgehoben und seine `action` in `hover` vermerkt
|
||||
/// (der Klick wirkt dann auf genau diese Aktion).
|
||||
fn button(
|
||||
ui: &mut Ui, font: &Font, rect: [f32; 4], label: &str, action: &str,
|
||||
cur: [f32; 2], hover: &mut Option<String>,
|
||||
) {
|
||||
let hot = in_rect(cur, rect);
|
||||
if hot { *hover = Some(action.to_string()); }
|
||||
let bg = if hot { [0.34, 0.22, 0.46, 1.0] } else { [0.15, 0.12, 0.22, 1.0] };
|
||||
ui.fill(rect, bg);
|
||||
let tx = rect[0] + (rect[2] - font.text_width(label)) * 0.5;
|
||||
let ty = rect[1] + (rect[3] - font.glyph_h) * 0.5;
|
||||
ui.text(font, tx.round(), ty.round(), label, [0.95, 0.95, 1.0, 1.0]);
|
||||
}
|
||||
|
||||
/// Innenabstand vom Panelrand zum Inhalt: 8px Ornamentrahmen + 3px Luft.
|
||||
const INSET: f32 = ORN_CORNER + 3.0;
|
||||
const PANEL_BG: [f32; 4] = [0.06, 0.05, 0.10, 1.0];
|
||||
const PANEL_FRAME: [f32; 4] = [0.85, 0.75, 0.95, 1.0];
|
||||
const TEXT: [f32; 4] = [0.92, 0.92, 1.0, 1.0];
|
||||
|
||||
/// Das Overlay für den aktuellen Frame bauen — verzweigt nach `session.mode`.
|
||||
pub(crate) fn layout(internal: [f32; 2], fonts: &Fonts, session: &Session, cur: &Cursor) -> Screen {
|
||||
let mut ui = Ui::default();
|
||||
let mut hover: Option<String> = None;
|
||||
let f = &fonts.cga; // CGA 8×8 ist der UI-Standard-Font.
|
||||
|
||||
// Demo-Panel (opak) mit Ornamentrahmen + beide Fonts zum Vergleich.
|
||||
ui.panel([8.0, 8.0, 216.0, 58.0], [0.06, 0.05, 0.10, 1.0], [0.85, 0.75, 0.95, 1.0]);
|
||||
ui.text(&fonts.ega, 16.0, 16.0, "EGA 8x14 — Schöne Grüße!", [0.9, 0.9, 1.0, 1.0]);
|
||||
ui.text(&fonts.cga, 16.0, 34.0, "CGA 8x8 — abcABC 0123 äöüß", [0.6, 1.0, 0.7, 1.0]);
|
||||
// Im Flycam-Modus (Maus gefangen) zählt die freie Sicht → kein Panel.
|
||||
// Sonst je nach Modus Menü oder Dialog. Hit-Tests laufen nur bei freier
|
||||
// Maus (im Dialog ist die Maus ohnehin nie gefangen).
|
||||
if !cur.grabbed {
|
||||
match &session.mode {
|
||||
// Spiel und Flycam: nur HUD/Cursor, kein Panel (beide laufen
|
||||
// ohnehin gefangen — dieser Zweig greift nur im Übergang).
|
||||
Mode::FirstPerson | Mode::Free => {}
|
||||
Mode::Menu => menu(&mut ui, f, internal, cur.pos, &mut hover),
|
||||
Mode::Dialog(d) => dialog(&mut ui, f, internal, d, cur.pos, &mut hover),
|
||||
}
|
||||
}
|
||||
|
||||
// Cursor zuletzt → liegt über allem. Gefangen (Flycam) → HUD-Pointer
|
||||
// mittig; sonst Maus-Cursor an der Mausposition.
|
||||
// Cursor zuletzt → über allem. Gefangen → HUD-Pointer mittig; sonst
|
||||
// Maus-Cursor, „interagierbar" wenn er über einem Klickziel steht.
|
||||
if cur.grabbed {
|
||||
ui.cursor(CUR_HUD, [internal[0] * 0.5, internal[1] * 0.5], [1.0; 4]);
|
||||
} else {
|
||||
ui.cursor(CUR_MOUSE, cur.pos, [1.0; 4]);
|
||||
let idx = if hover.is_some() { CUR_MOUSE_INTERACT } else { CUR_MOUSE };
|
||||
ui.cursor(idx, cur.pos, [1.0; 4]);
|
||||
}
|
||||
|
||||
ui
|
||||
Screen { ui, hover_action: hover }
|
||||
}
|
||||
|
||||
/// Menu-Modus: das (noch demohafte) Menü, zentriert.
|
||||
fn menu(ui: &mut Ui, f: &Font, internal: [f32; 2], cur: [f32; 2], hover: &mut Option<String>) {
|
||||
let items = [
|
||||
("Fortsetzen", "menu"), // schließt das Menü → ins Spiel (Play)
|
||||
("Hilfe", "help"),
|
||||
("KV anzeigen", "kv"),
|
||||
("Beenden", "quit"),
|
||||
];
|
||||
let (bw, bh, gap) = (112.0, 14.0, 4.0);
|
||||
let title_h = f.glyph_h;
|
||||
let body_h = title_h + gap + items.len() as f32 * bh + (items.len() as f32 - 1.0) * gap;
|
||||
let pw = bw + 2.0 * INSET;
|
||||
let ph = body_h + 2.0 * INSET;
|
||||
let px = ((internal[0] - pw) * 0.5).round();
|
||||
let py = ((internal[1] - ph) * 0.5).round();
|
||||
|
||||
ui.panel([px, py, pw, ph], PANEL_BG, PANEL_FRAME);
|
||||
ui.text(f, px + INSET, py + INSET, "Menü", [0.85, 0.8, 0.6, 1.0]);
|
||||
|
||||
let bx = px + INSET;
|
||||
let mut by = py + INSET + title_h + gap;
|
||||
for (label, action) in items {
|
||||
button(ui, f, [bx, by, bw, bh], label, action, cur, hover);
|
||||
by += bh + gap;
|
||||
}
|
||||
}
|
||||
|
||||
/// Dialog-Modus: Textpanel unten, darunter ein Button je Choice. Ohne Choices
|
||||
/// ein „Weiter"-Button, dessen Aktion die Leereingabe ist (blättert weiter) —
|
||||
/// derselbe Pfad wie Enter im Terminal (`dialog_input`).
|
||||
fn dialog(ui: &mut Ui, f: &Font, internal: [f32; 2], d: &Dialog, cur: [f32; 2], hover: &mut Option<String>) {
|
||||
let margin = 16.0;
|
||||
let (bh, gap) = (14.0, 4.0);
|
||||
let pw = internal[0] - 2.0 * margin;
|
||||
let line_h = f.glyph_h + 2.0;
|
||||
|
||||
// Text auf die Panelbreite umbrechen.
|
||||
let lines = wrap(f, &d.text, pw - 2.0 * INSET);
|
||||
let text_h = lines.len() as f32 * line_h;
|
||||
|
||||
// Buttonzeilen: je Choice eine, mindestens die „Weiter"-Zeile.
|
||||
let n = d.choices.len().max(1) as f32;
|
||||
let buttons_h = n * bh + (n - 1.0) * gap;
|
||||
|
||||
let ph = 2.0 * INSET + text_h + gap + buttons_h;
|
||||
let py = (internal[1] - margin - ph).max(margin).round();
|
||||
let px = margin;
|
||||
|
||||
ui.panel([px, py, pw, ph], PANEL_BG, PANEL_FRAME);
|
||||
|
||||
let mut ty = py + INSET;
|
||||
for line in &lines {
|
||||
ui.text(f, px + INSET, ty, line, TEXT);
|
||||
ty += line_h;
|
||||
}
|
||||
|
||||
let (cbx, cbw) = (px + INSET, pw - 2.0 * INSET);
|
||||
let mut cby = ty + gap;
|
||||
if d.choices.is_empty() {
|
||||
button(ui, f, [cbx, cby, cbw, bh], "Weiter", "", cur, hover);
|
||||
} else {
|
||||
for (i, opt) in d.choices.iter().enumerate() {
|
||||
let label = format!("{}) {opt}", i + 1);
|
||||
button(ui, f, [cbx, cby, cbw, bh], &label, &(i + 1).to_string(), cur, hover);
|
||||
cby += bh + gap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Text in Zeilen umbrechen, die in `max_w` Pixel passen. Vorhandene
|
||||
/// `\n` sind harte Umbrüche; sonst wird wortweise gegriffen. Ein Wort breiter
|
||||
/// als `max_w` bleibt allein in seiner Zeile (Überlauf statt Endlosschleife).
|
||||
fn wrap(font: &Font, text: &str, max_w: f32) -> Vec<String> {
|
||||
let mut lines = Vec::new();
|
||||
for para in text.split('\n') {
|
||||
let mut line = String::new();
|
||||
for word in para.split_whitespace() {
|
||||
let trial = if line.is_empty() { word.to_string() } else { format!("{line} {word}") };
|
||||
if line.is_empty() || font.text_width(&trial) <= max_w {
|
||||
line = trial;
|
||||
} else {
|
||||
lines.push(std::mem::take(&mut line));
|
||||
line = word.to_string();
|
||||
}
|
||||
}
|
||||
lines.push(line);
|
||||
}
|
||||
lines
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -235,6 +368,37 @@ mod tests {
|
||||
assert_eq!(&k.rgba[4..8], &[255, 255, 255, 0]); // schwarz → transparent
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn button_registers_hover_only_when_cursor_inside() {
|
||||
// Leerer 256×64-Atlas → CGA-Font mit lauter leeren Glyphen; text_width
|
||||
// bleibt endlich, Button funktioniert trotzdem.
|
||||
let atlas = Image { width: 256, height: 64, rgba: vec![0; 256 * 64 * 4] };
|
||||
let font = Font::cga(FONT_CGA, &atlas);
|
||||
let rect = [10.0, 10.0, 100.0, 20.0];
|
||||
|
||||
let mut ui = Ui::default();
|
||||
let mut hover = None;
|
||||
button(&mut ui, &font, rect, "X", "do_x", [50.0, 15.0], &mut hover);
|
||||
assert_eq!(hover.as_deref(), Some("do_x"));
|
||||
|
||||
let mut outside = None;
|
||||
button(&mut ui, &font, rect, "X", "do_x", [200.0, 15.0], &mut outside);
|
||||
assert!(outside.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrap_breaks_on_width_and_hard_newlines() {
|
||||
// Leerer Atlas → jedes Zeichen rückt um space_adv (cga: 4px) vor.
|
||||
let atlas = Image { width: 256, height: 64, rgba: vec![0; 256 * 64 * 4] };
|
||||
let f = Font::cga(FONT_CGA, &atlas);
|
||||
// 3-Zeichen-Wörter = 12px. Bei max 12 passt je ein Wort pro Zeile.
|
||||
assert_eq!(wrap(&f, "aaa bbb ccc", 12.0), vec!["aaa", "bbb", "ccc"]);
|
||||
// Bei max 28 passen zwei ("aaa bbb" = 7·4 = 28).
|
||||
assert_eq!(wrap(&f, "aaa bbb ccc", 28.0), vec!["aaa bbb", "ccc"]);
|
||||
// Harte Umbrüche bleiben erhalten.
|
||||
assert_eq!(wrap(&f, "a\nb", 999.0), vec!["a", "b"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cursor_uv_picks_quadrant() {
|
||||
// idx 3 = (col 1, row 1) → rechte untere 16×16-Zelle.
|
||||
|
||||
@@ -1,33 +1,20 @@
|
||||
//! Session: der geteilte Anwendungszustand über allen Frontends.
|
||||
//!
|
||||
//! Es existiert genau eine `Session` pro Lauf. CLI und Fenster steuern
|
||||
//! dieselbe — das Fenster besitzt sie, die Terminal-Eingabe schickt nur
|
||||
//! Befehlszeilen hinein. Dadurch kann es keinen Mismatch geben: ein
|
||||
//! `Game`-Zustand, ein `Mode`, beide Frontends stellen ihn nur dar.
|
||||
//!
|
||||
//! Schicht: `engine ← session ← { cli, render }`. Die Session kennt kein
|
||||
//! Frontend — `exec` nimmt eine Befehlszeile und *gibt* Ausgabezeilen
|
||||
//! zurück (statt zu drucken), damit jedes Frontend sie frei darstellt
|
||||
//! (Terminal jetzt, In-Fenster-Konsole später).
|
||||
|
||||
use crate::engine::game::{Action, Game};
|
||||
use crate::engine::collision::CollisionWorld;
|
||||
use crate::engine::game::{Action, Game, ModeTarget};
|
||||
use crate::engine::ink::StoryState;
|
||||
use crate::engine::map::{self, Map};
|
||||
use crate::engine::player::Player;
|
||||
use crate::engine::{kv, signals, story_ctrl};
|
||||
|
||||
/// Was die Frontends gerade darstellen sollen. Geteilt, damit CLI und GUI
|
||||
/// nie uneins sind, ob ein Dialog läuft.
|
||||
/// Maus-Empfindlichkeit der Spieler-Sicht (Radiant/Pixel).
|
||||
const LOOK_SENS: f32 = 0.0025;
|
||||
|
||||
pub enum Mode {
|
||||
/// Freie Welt — das Fenster rendert die Szene, die Konsole nimmt
|
||||
/// Engine-Befehle.
|
||||
FirstPerson,
|
||||
Free,
|
||||
/// Ein Dialog läuft — das Fenster pausiert die Welt und zeigt (sobald
|
||||
/// Text-Rendering existiert) Panels; Eingaben treiben den Dialog statt
|
||||
/// Befehle.
|
||||
Menu,
|
||||
Dialog(Dialog),
|
||||
}
|
||||
|
||||
/// Aktueller Dialogschritt, frontend-neutral. `choices` leer = reiner Text,
|
||||
/// der auf „weiter" (Leerzeile/Enter) wartet.
|
||||
pub struct Dialog {
|
||||
// Wird vom Panel-Renderer der UI-Phase gelesen; bis dahin läuft der
|
||||
// Dialogtext über die Konsolen-Ausgabe (siehe `step`).
|
||||
@@ -39,9 +26,23 @@ pub struct Dialog {
|
||||
pub struct Session {
|
||||
pub game: Game,
|
||||
pub mode: Mode,
|
||||
pub player: Player,
|
||||
pub collision: CollisionWorld,
|
||||
signals_path: String,
|
||||
}
|
||||
|
||||
/// Kontinuierliche Eingabe für einen Simulationsschritt — vom Frontend pro
|
||||
/// Frame gefüllt. Gegenstück zu [`ExecResult`]/`exec` (diskret). `look_*`
|
||||
/// sind rohe Maus-Pixel, `fwd`/`right` Tastenachsen in [-1, 1].
|
||||
#[derive(Default)]
|
||||
pub struct FrameInput {
|
||||
pub fwd: f32,
|
||||
pub right: f32,
|
||||
pub jump: bool,
|
||||
pub look_dx: f32,
|
||||
pub look_dy: f32,
|
||||
}
|
||||
|
||||
/// Ergebnis einer Eingabezeile: Ausgabezeilen plus, ob das Frontend
|
||||
/// beenden soll (`quit`/`exit`).
|
||||
pub struct ExecResult {
|
||||
@@ -57,7 +58,33 @@ impl ExecResult {
|
||||
impl Session {
|
||||
pub fn new(signals_path: String) -> Self {
|
||||
let game = Game::new(signals::load_signals(&signals_path));
|
||||
Self { game, mode: Mode::Free, signals_path }
|
||||
Self {
|
||||
game,
|
||||
mode: Mode::Menu,
|
||||
// Default-Welt, bis `load_world` eine Map einspielt (die CLI
|
||||
// braucht keine — sie tickt nie).
|
||||
player: Player::new([0.0, 1.0, 0.0]),
|
||||
collision: CollisionWorld::empty(),
|
||||
signals_path,
|
||||
}
|
||||
}
|
||||
|
||||
/// Welt-Geometrie aus der Map laden: Collision-Brushes bauen und den
|
||||
/// Spieler an `info_player_start` setzen. Einmal vom Fenster-Frontend beim
|
||||
/// Start gerufen.
|
||||
pub fn load_world(&mut self, map: &Map) {
|
||||
self.collision = CollisionWorld::build(map);
|
||||
self.player = Player::new(player_spawn(map));
|
||||
}
|
||||
|
||||
/// Einen Simulationsschritt treiben — das kontinuierliche Gegenstück zu
|
||||
/// `exec`. Nur im Spielmodus bewegt sich der Spieler; Menü/Dialog/Flycam
|
||||
/// pausieren die Sim (die Flycam ist reine Frontend-Sicht).
|
||||
pub fn tick(&mut self, input: &FrameInput, dt: f32) {
|
||||
if matches!(self.mode, Mode::FirstPerson) {
|
||||
self.player.look(input.look_dx, input.look_dy, LOOK_SENS);
|
||||
self.player.step(&self.collision, input.fwd, input.right, input.jump, dt);
|
||||
}
|
||||
}
|
||||
|
||||
/// Reserviertes `[init]`-Signal feuern (KV-Defaults, bevor etwas läuft).
|
||||
@@ -73,8 +100,8 @@ impl Session {
|
||||
/// Klick denselben Dialog ohne Sonderpfad.
|
||||
pub fn exec(&mut self, line: &str) -> ExecResult {
|
||||
match self.mode {
|
||||
Mode::Dialog(_) => self.dialog_input(line),
|
||||
Mode::Free => self.command(line),
|
||||
Mode::Dialog(_) => self.dialog_input(line),
|
||||
Mode::FirstPerson | Mode::Free | Mode::Menu => self.command(line),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +110,27 @@ impl Session {
|
||||
match line {
|
||||
"" => ExecResult::empty(),
|
||||
"quit" | "exit" => ExecResult { output: Vec::new(), quit: true },
|
||||
"menu" => {
|
||||
// Menü öffnen bzw. ins Spiel zurück. „Fortsetzen" landet im
|
||||
// Spielmodus (Play), nicht in der Debug-Flycam; aus Play oder
|
||||
// Free öffnet es das Menü. (Dialog wird anders geroutet.)
|
||||
let t = match self.mode {
|
||||
Mode::Menu => ModeTarget::Play,
|
||||
_ => ModeTarget::Menu,
|
||||
};
|
||||
self.set_mode(t);
|
||||
ExecResult::empty()
|
||||
}
|
||||
"noclip" => {
|
||||
// Debug: zwischen First-Person (Play) und freier Flycam (Free)
|
||||
// umschalten.
|
||||
let t = match self.mode {
|
||||
Mode::FirstPerson => ModeTarget::Free,
|
||||
_ => ModeTarget::Play,
|
||||
};
|
||||
self.set_mode(t);
|
||||
ExecResult::empty()
|
||||
}
|
||||
"help" => ExecResult::lines(help()),
|
||||
"kv" => ExecResult::lines(self.dump_kv()),
|
||||
"reload" => {
|
||||
@@ -92,7 +140,20 @@ impl Session {
|
||||
])
|
||||
}
|
||||
_ => {
|
||||
if let Some(sig) = line.strip_prefix("signal ") {
|
||||
if let Some(m) = line.strip_prefix("mode ") {
|
||||
// Modus explizit setzen — über dieselbe Action wie das
|
||||
// `mode`-Verb aus signals.toml/Ink-Tags, damit CLI und
|
||||
// Signals genau einen Wechsel-Pfad teilen.
|
||||
match ModeTarget::parse(m) {
|
||||
Some(t) => {
|
||||
self.game.actions.push(Action::SetMode(t));
|
||||
ExecResult::lines(self.drain_actions())
|
||||
}
|
||||
None => ExecResult::lines(vec![
|
||||
"Modus: play | free | menu".into(),
|
||||
]),
|
||||
}
|
||||
} else if let Some(sig) = line.strip_prefix("signal ") {
|
||||
ExecResult::lines(self.fire(sig.trim(), None))
|
||||
} else if let Some(name) = line.strip_prefix("use ") {
|
||||
// Objekt-Interaktion simulieren: Signal ist der gestrippte
|
||||
@@ -119,7 +180,15 @@ impl Session {
|
||||
_ => return ExecResult::lines(vec![format!(" (1..{})", d.choices.len())]),
|
||||
}
|
||||
};
|
||||
ExecResult::lines(self.step(sel))
|
||||
let mut out = self.step(sel);
|
||||
// Ein start_ink-Tag kann während step() eine neue Story in den Slot
|
||||
// geladen haben, während die alte auf End lief (mode → Play). Falls
|
||||
// das passiert ist, sofort den ersten Schritt der neuen Story ziehen
|
||||
// statt den Spieler im Play-Modus hängen zu lassen.
|
||||
if self.game.story.is_some() && matches!(self.mode, Mode::FirstPerson) {
|
||||
out.extend(self.step(None));
|
||||
}
|
||||
ExecResult::lines(out)
|
||||
}
|
||||
|
||||
/// Signal dispatchen, deferred Actions einsammeln und — falls dadurch
|
||||
@@ -149,7 +218,7 @@ impl Session {
|
||||
let mut out = self.drain_actions();
|
||||
match state {
|
||||
None | Some(StoryState::End) => {
|
||||
self.mode = Mode::Free;
|
||||
self.mode = Mode::FirstPerson;
|
||||
}
|
||||
Some(StoryState::Text(text)) => {
|
||||
out.push(text.clone());
|
||||
@@ -167,10 +236,33 @@ impl Session {
|
||||
}
|
||||
|
||||
fn drain_actions(&mut self) -> Vec<String> {
|
||||
self.game.actions.drain(..).map(|a| match a {
|
||||
Action::HideObject(n) => format!("[action] hide_object {n}"),
|
||||
Action::PlaySound(n) => format!("[action] play_sound {n}"),
|
||||
}).collect()
|
||||
// Erst aus der Queue ziehen (gibt den `&mut self.game`-Borrow frei),
|
||||
// dann anwenden — `SetMode` braucht `&mut self.mode`.
|
||||
let actions: Vec<Action> = self.game.actions.drain(..).collect();
|
||||
let mut out = Vec::with_capacity(actions.len());
|
||||
for a in actions {
|
||||
match a {
|
||||
Action::HideObject(n) => out.push(format!("[action] hide_object {n}")),
|
||||
Action::PlaySound(n) => out.push(format!("[action] play_sound {n}")),
|
||||
Action::SetMode(t) => {
|
||||
self.set_mode(t);
|
||||
out.push(format!("[action] mode {}", t.label()));
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Frontend-neutrales [`ModeTarget`] auf den konkreten [`Mode`] mappen.
|
||||
/// Einziger Schreibpfad für die nicht-Dialog-Modi — `menu`/`noclip`/`mode`
|
||||
/// und die `SetMode`-Action laufen alle hier durch. (Der Dialog-Modus
|
||||
/// entsteht ausschließlich in `step` aus dem Story-Ablauf.)
|
||||
fn set_mode(&mut self, t: ModeTarget) {
|
||||
self.mode = match t {
|
||||
ModeTarget::Play => Mode::FirstPerson,
|
||||
ModeTarget::Free => Mode::Free,
|
||||
ModeTarget::Menu => Mode::Menu,
|
||||
};
|
||||
}
|
||||
|
||||
fn dump_kv(&self) -> Vec<String> {
|
||||
@@ -182,6 +274,21 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn-Fußpunkt (Engine-Koords) aus `info_player_start`; sonst ein Default.
|
||||
fn player_spawn(map: &Map) -> [f32; 3] {
|
||||
for e in &map.entities {
|
||||
if e.classname() == Some("info_player_start") {
|
||||
if let Some(origin) = e.props.get("origin") {
|
||||
let v: Vec<f32> = origin.split_whitespace().filter_map(|s| s.parse().ok()).collect();
|
||||
if let [x, y, z] = v[..] {
|
||||
return map::to_engine([x, y, z]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
[0.0, 1.0, 0.0]
|
||||
}
|
||||
|
||||
fn help() -> Vec<String> {
|
||||
vec![
|
||||
" signal <s> Signal feuern oder Action direkt ausführen".into(),
|
||||
@@ -189,6 +296,9 @@ fn help() -> Vec<String> {
|
||||
" use <instance> Objekt-Interaktion simulieren — `use Mushroom.005`".into(),
|
||||
" feuert Signal `Mushroom` mit $self = Mushroom.005".into(),
|
||||
" kv KV-Store anzeigen".into(),
|
||||
" menu Menü öffnen / ins Spiel zurück".into(),
|
||||
" noclip zwischen First-Person und Debug-Flycam wechseln".into(),
|
||||
" mode <m> Modus direkt setzen: play | free | menu".into(),
|
||||
" reload signals.toml neu laden".into(),
|
||||
" quit beenden".into(),
|
||||
]
|
||||
@@ -199,9 +309,15 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
// Fehlender Pfad → leere Signal-Table (load_signals schluckt den Fehler),
|
||||
// also keine Asset-Abhängigkeit für diese Tests.
|
||||
// also keine Asset-Abhängigkeit für diese Tests. Der Konstruktor startet
|
||||
// im Menü (Titelbildschirm); Tests des Befehlspfads setzen vorab `Free`.
|
||||
fn empty_session() -> Session { Session::new("/nonexistent/signals.toml".into()) }
|
||||
|
||||
#[test]
|
||||
fn starts_in_menu() {
|
||||
assert!(matches!(empty_session().mode, Mode::Menu));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quit_sets_flag() {
|
||||
let mut s = empty_session();
|
||||
@@ -211,6 +327,7 @@ mod tests {
|
||||
#[test]
|
||||
fn builtin_command_mutates_kv_and_stays_free() {
|
||||
let mut s = empty_session();
|
||||
s.mode = Mode::Free;
|
||||
// Unbekanntes Signal fällt auf den Builtin-Pfad durch → `set`.
|
||||
let r = s.exec("signal set has_key true");
|
||||
assert!(!r.quit);
|
||||
@@ -218,9 +335,59 @@ mod tests {
|
||||
assert!(matches!(s.mode, Mode::Free));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn menu_command_opens_and_resumes_to_play() {
|
||||
let mut s = empty_session();
|
||||
s.mode = Mode::Free;
|
||||
s.exec("menu");
|
||||
assert!(matches!(s.mode, Mode::Menu));
|
||||
s.exec("menu"); // „Fortsetzen" → ins Spiel
|
||||
assert!(matches!(s.mode, Mode::FirstPerson));
|
||||
s.exec("menu"); // aus dem Spiel wieder ins Menü
|
||||
assert!(matches!(s.mode, Mode::Menu));
|
||||
// Im Menü laufen Engine-Befehle weiter (gleiche Route wie Play/Free).
|
||||
s.exec("signal set in_menu true");
|
||||
assert!(s.game.kv["in_menu"].coerce_to_bool().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mode_command_sets_mode_directly() {
|
||||
let mut s = empty_session();
|
||||
s.exec("mode play");
|
||||
assert!(matches!(s.mode, Mode::FirstPerson));
|
||||
s.exec("mode free");
|
||||
assert!(matches!(s.mode, Mode::Free));
|
||||
s.exec("mode menu");
|
||||
assert!(matches!(s.mode, Mode::Menu));
|
||||
// Müll-Argument lässt den Modus stehen und meldet die Nutzung.
|
||||
let r = s.exec("mode dialog");
|
||||
assert!(matches!(s.mode, Mode::Menu));
|
||||
assert_eq!(r.output.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signal_can_switch_mode() {
|
||||
let mut s = empty_session();
|
||||
s.mode = Mode::Free;
|
||||
// `mode`-Verb direkt als Action (Ink-Tag-/signals.toml-Pfad).
|
||||
s.exec("signal mode play");
|
||||
assert!(matches!(s.mode, Mode::FirstPerson));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noclip_toggles_play_and_free() {
|
||||
let mut s = empty_session();
|
||||
s.mode = Mode::FirstPerson;
|
||||
s.exec("noclip");
|
||||
assert!(matches!(s.mode, Mode::Free));
|
||||
s.exec("noclip");
|
||||
assert!(matches!(s.mode, Mode::FirstPerson));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_command_reports_and_stays_free() {
|
||||
let mut s = empty_session();
|
||||
s.mode = Mode::Free;
|
||||
let r = s.exec("frobnicate");
|
||||
assert_eq!(r.output.len(), 1);
|
||||
assert!(matches!(s.mode, Mode::Free));
|
||||
|
||||
@@ -54,12 +54,37 @@ if [ ! -d "$ink_dir" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Prüft eine .ink-Datei auf Zeichen außerhalb von CP437 und gibt sie aus.
|
||||
# Exit-Code 1 wenn Treffer gefunden, sonst 0.
|
||||
check_cp437() {
|
||||
python3 - "$1" <<'PYEOF'
|
||||
import sys
|
||||
path = sys.argv[1]
|
||||
cp437 = set(bytes(range(256)).decode('cp437', errors='replace'))
|
||||
hits = []
|
||||
with open(path, encoding='utf-8') as f:
|
||||
for lineno, line in enumerate(f, 1):
|
||||
for ch in line:
|
||||
if ord(ch) > 127 and ch not in cp437:
|
||||
hits.append(f' Z.{lineno}: U+{ord(ch):04X} {repr(ch)}')
|
||||
for h in hits:
|
||||
print(h)
|
||||
sys.exit(1 if hits else 0)
|
||||
PYEOF
|
||||
}
|
||||
|
||||
built=0 skipped=0 failed=0
|
||||
shopt -s nullglob
|
||||
for src in "$ink_dir"/*.ink; do
|
||||
out="${src%.ink}.ink.json"
|
||||
name="$(basename "$src")"
|
||||
|
||||
# CP437-Warnung: läuft immer, unabhängig vom Build-Status.
|
||||
if ! cp437_warn="$(check_cp437 "$src")"; then
|
||||
printf ' WARNUNG %s -- Nicht-CP437-Zeichen:\n' "$name"
|
||||
printf '%s\n' "$cp437_warn"
|
||||
fi
|
||||
|
||||
# Inkrementell: überspringen, wenn Ausgabe existiert und neuer ist.
|
||||
if [ "$force" -eq 0 ] && [ -f "$out" ] && [ "$out" -nt "$src" ]; then
|
||||
printf ' aktuell %s\n' "$name"
|
||||
|
||||