From 6eefa946bf05660c13a6324b79eaeb9f22191c5d Mon Sep 17 00:00:00 2001 From: SamsTheNerd Date: Sat, 30 Dec 2023 19:16:56 -0500 Subject: [PATCH 1/3] add flag handling --- src/hexdoc/_templates/category.html.jinja | 4 +- src/hexdoc/_templates/entry.html.jinja | 5 +- .../pages/patchouli/page.html.jinja | 4 +- src/hexdoc/patchouli/category.py | 3 +- src/hexdoc/patchouli/entry.py | 3 +- src/hexdoc/patchouli/flag.py | 53 +++++++++++++++++++ src/hexdoc/patchouli/page/abstract_pages.py | 3 +- 7 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 src/hexdoc/patchouli/flag.py diff --git a/src/hexdoc/_templates/category.html.jinja b/src/hexdoc/_templates/category.html.jinja index 155161727..15d0f21e6 100644 --- a/src/hexdoc/_templates/category.html.jinja +++ b/src/hexdoc/_templates/category.html.jinja @@ -1,6 +1,8 @@ {% import "macros/formatting.html.jinja" as fmt with context %} -
+
{% call fmt.maybe_spoilered(category) %} {{- fmt.section_header(category, "h2", "category-title") }} {{ fmt.styled(category.description) }} diff --git a/src/hexdoc/_templates/entry.html.jinja b/src/hexdoc/_templates/entry.html.jinja index 443c58d10..bb542b58f 100644 --- a/src/hexdoc/_templates/entry.html.jinja +++ b/src/hexdoc/_templates/entry.html.jinja @@ -1,6 +1,9 @@ {% import "macros/formatting.html.jinja" as fmt -%} -
+
{% call fmt.maybe_spoilered(entry) %} {{- fmt.section_header(entry, "h3", "entry-title") }} diff --git a/src/hexdoc/_templates/pages/patchouli/page.html.jinja b/src/hexdoc/_templates/pages/patchouli/page.html.jinja index 939c8019a..705fa8633 100644 --- a/src/hexdoc/_templates/pages/patchouli/page.html.jinja +++ b/src/hexdoc/_templates/pages/patchouli/page.html.jinja @@ -3,7 +3,9 @@ {% set page_anchor_id = entry.id.path ~ "@" ~ page.anchor %} {#- page content (not required because EmptyPage uses this template directly) #} -
+
{% block body scoped %}{% endblock %}
{% else %} diff --git a/src/hexdoc/patchouli/category.py b/src/hexdoc/patchouli/category.py index 901b4e35c..0fc523158 100644 --- a/src/hexdoc/patchouli/category.py +++ b/src/hexdoc/patchouli/category.py @@ -10,6 +10,7 @@ from hexdoc.utils import Sortable, sorted_dict from .entry import Entry +from .flag import FlagExpression from .text import FormatTree @@ -30,7 +31,7 @@ class Category(IDModel, Sortable): # optional parent_id: ResourceLocation | None = Field(default=None, alias="parent") _parent_cmp_key: tuple[int, ...] | None = None - flag: str | None = None + flag: FlagExpression | None = None sortnum: int = 0 secret: bool = False diff --git a/src/hexdoc/patchouli/entry.py b/src/hexdoc/patchouli/entry.py index 21ddfdc52..6e47fb07f 100644 --- a/src/hexdoc/patchouli/entry.py +++ b/src/hexdoc/patchouli/entry.py @@ -10,6 +10,7 @@ from hexdoc.utils import Sortable from .book_context import BookContext +from .flag import FlagExpression from .page import CraftingPage, Page, PageWithTitle from .text import FormatTree @@ -30,7 +31,7 @@ class Entry(IDModel, Sortable): # optional (entry.json) advancement: ResourceLocation | None = None - flag: str | None = None + flag: FlagExpression | None = None priority: bool = False secret: bool = False read_by_default: bool = False diff --git a/src/hexdoc/patchouli/flag.py b/src/hexdoc/patchouli/flag.py new file mode 100644 index 000000000..1dc733327 --- /dev/null +++ b/src/hexdoc/patchouli/flag.py @@ -0,0 +1,53 @@ +from typing import Any + +from pydantic import model_validator + +from hexdoc.model import HexdocModel + + +class Flag(HexdocModel): + name: str + negated: bool = False + + @model_validator(mode="before") + @classmethod + def parse_flag(cls, data: Any) -> Any: + if isinstance(data, str): + assert ( + "," not in data + ) # not sure if there are other invalid characters or not + if data.startswith("!"): + return {"name": data[1:], "negated": True} + return {"name": data} + + return data + + def css_classname(self) -> str: + base = "flag-" + self.name.replace(":", "-") + if self.negated: + return "not-" + base + return base + + +class FlagExpression(HexdocModel): + flags: list[Flag] + conjuctive: bool = True + + @model_validator(mode="before") + @classmethod + def parse_flags(cls, data: Any) -> Any: + if isinstance(data, str): + if data.startswith("|") or data.startswith("&"): # must be a list + return { + "flags": data[1:].split(","), + "conjuctive": data.startswith("&"), + } + return {"flags": [data]} + + return data + + def css_classnames(self) -> str: + flagclasses = " ".join(map(lambda f: f.css_classname(), self.flags)) + if self.conjuctive: + return "flagall " + flagclasses + return "flagany " + flagclasses diff --git a/src/hexdoc/patchouli/page/abstract_pages.py b/src/hexdoc/patchouli/page/abstract_pages.py index ce0e64519..dc9af6e9e 100644 --- a/src/hexdoc/patchouli/page/abstract_pages.py +++ b/src/hexdoc/patchouli/page/abstract_pages.py @@ -9,6 +9,7 @@ from hexdoc.model import TypeTaggedTemplate from hexdoc.utils import Inherit, InheritType, NoValue, classproperty +from ..flag import FlagExpression from ..text import FormatTree _T_Recipe = TypeVar("_T_Recipe", bound=Recipe) @@ -21,7 +22,7 @@ class Page(TypeTaggedTemplate, type=None): """ advancement: ResourceLocation | None = None - flag: str | None = None + flag: FlagExpression | None = None anchor: str | None = None def __init_subclass__( From 98efb3d6a767357589003d3724f620da1caf8a04 Mon Sep 17 00:00:00 2001 From: SamsTheNerd Date: Sat, 30 Dec 2023 20:02:08 -0500 Subject: [PATCH 2/3] switch from map to generator in flag css name function --- src/hexdoc/patchouli/flag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hexdoc/patchouli/flag.py b/src/hexdoc/patchouli/flag.py index 1dc733327..a56425fca 100644 --- a/src/hexdoc/patchouli/flag.py +++ b/src/hexdoc/patchouli/flag.py @@ -47,7 +47,7 @@ def parse_flags(cls, data: Any) -> Any: return data def css_classnames(self) -> str: - flagclasses = " ".join(map(lambda f: f.css_classname(), self.flags)) + flagclasses = " ".join(flag.css_classname() for flag in self.flags) if self.conjuctive: return "flagall " + flagclasses return "flagany " + flagclasses From dc704e8302ff84fe5ac459a995bb16645cf140bb Mon Sep 17 00:00:00 2001 From: SamsTheNerd Date: Sat, 30 Dec 2023 20:56:20 -0500 Subject: [PATCH 3/3] flag snapshots --- ...test_index[vlatestmainen_usindex.html].raw | 10 +- ...test_files[vlatestmainen_usindex.html].raw | 621 ++++++++++-------- 2 files changed, 357 insertions(+), 274 deletions(-) diff --git a/test/integration/__snapshots__/test_copier/test_index[vlatestmainen_usindex.html].raw b/test/integration/__snapshots__/test_copier/test_index[vlatestmainen_usindex.html].raw index b40d97a1a..a8b58dec0 100644 --- a/test/integration/__snapshots__/test_copier/test_index[vlatestmainen_usindex.html].raw +++ b/test/integration/__snapshots__/test_copier/test_index[vlatestmainen_usindex.html].raw @@ -166,7 +166,7 @@
-
+

The practitioners of this art would cast their so-called Hexes by drawing strange patterns in the air with a Staff -- or craft powerful magical items to do the casting for them. How might I do the same?

-
+

-
+

The practitioners of this art would cast their so-called Hexes by drawing strange patterns in the air with a Staff -- or craft powerful magical items to do the casting for them. How might I do the same?

-
+

-
+

The practitioners of this art would cast their so-called Hexes by drawing strange patterns in the air with a Staff -- or craft powerful magical items to do the casting for them. How might I do the same?

-
+

Sadly, even a fully sentient being (like myself, presumably) can only generate miniscule amounts of media. It would be quite impractical to try and use my own brainpower to cast Hexes.

But legend has it that there are underground deposits where media slowly accumulates, growing into crystalline forms.

If I could just find one of those...


-
+

As I take the beauty of the crystal in, I can feel connections flashing wildly in my mind. It's like the media in the air is entering me, empowering me, elucidating me... It feels wonderful.

Finally, my study into the arcane is starting to make some sense!

Let me reread those old legends again, now that I know what I'm looking at.


-
+

-
+

-
+

I've started to understand how the old masters cast their Hexes! It's a bit complicated, but I'm sure I can figure it out. Let's see...

-
+

Finally, it seems spells have a maximum range of influence, about 32 blocks from my position. Trying to affect anything outside of that will cause the spell to fail.

Despite this, if I have a player's reference, I can affect them from anywhere. This only applies to affecting them directly, though; I cannot use this to affect the world around them if they're outside of my range.
I ought to be careful when giving out a reference like that. While friendly Hexcasters could use them to great effect and utility, I shudder to think of what someone malicious might do with this.


-
+


-
+

A bug in the mod caused an iota of an invalid type or otherwise caused the spell to crash. Please open a bug report!

Causes black sparks.


-
+

Even more complicated actions can be expressed in terms of pushing, popping, and peeking. For example, Jester's Gambit swaps the top two items of the stack. This can be thought of as popping two items and pushing them in opposite order. For another, Gemini Decomposition duplicates the top of the stack-- in other words, it peeks the stack and pushes a copy of what it finds.


-
+

Spells seem to be exempt from this nomenclature and are more or less named after what they do-- after all, why call it a Demoman's Gambit when you could just say Explosion?


-
+

Finally, there seems to be an infinite family of influences that just seem to be a tangled mess of media. I've named them Garbage, as they are completely useless. They seem to appear in my stack at various places in response to mishaps, and appear to my senses as a nonsense jumble.


-
+

-
+

I devote this section to the magical and mysterious items I might encounter in my studies.

-
+

-
+
It seems that I'll find three different forms of amethyst when breaking a crystal inside a geode. The smallest denomination seems to be a small pile of shimmering dust, worth a relatively small amount of media.


-
+
The second is a whole shard of amethyst, of the type non-Hexcasters might be used to. This has about as much media inside as five Amethyst Dust.


-
+
The old man sighed and raised a hand toward the fire. He unlocked a part of his brain that held the memories of the mountains around them. He pulled the energies from those lands, as he learned to do in Terisia City with Drafna, Hurkyl, the archimandrite, and the other mages of the Ivory Towers. He concentrated, and the flames writhed as they rose from the logs, twisting upon themselves until they finally formed a soft smile.


-
+

Don't fight; flame, light; ignite; burn bright.


-
+

You must learn... to see what you are looking at.


-
+

How would you feel if someone saw you wearing a sign that said, "I am dashing and handsome?"


-
+

Poison apples, poison worms.


-
+

Mathematics? That's for eggheads!


-
+

Wizards love words. Most of them read a great deal, and indeed one strong sign of a potential wizard is the inability to get to sleep without reading something first.


-
+

I write upon clean white parchment with a sharp quill and the blood of my students, divining their secrets.


-
+

I'm also aware of other types of Slates, slates that do not contain patterns but seem to be inlaid with other ... strange ... oddities. It hurts my brain to think about them, as if my thoughts get bent around their designs, following their pathways, bending and wefting through their labyrinthine depths, through and through and through channeled through and processed and--

... I almost lost myself. Maybe I should postpone my studies of those.


-
+

Each infusion spell requires an entity and a list of patterns on the stack. The entity must be a media-holding item entity (i.e. amethyst crystals, dropped on the ground); the entity is consumed and forms the battery.

Usefully, it seems that the media in the battery is not consumed in chunks as it is when casting with a Staff-- rather, the media "melts down" into one continuous pool. Thus, if I store a Hex that only costs one Amethyst Dust's worth of media, a Charged Crystal used as the battery will allow me to cast it 10 times.


-
+
Click to show recipes @@ -3228,7 +3249,7 @@

-
+
Click to show recipes @@ -3327,7 +3348,8 @@

-
+

Drink the milk.


-
+


-
+

Their smooth trunks, with white bark, gave the effect of enormous columns sustaining the weight of an immense foliage, full of shade and silence.


-
+

Carefully, she cracked the half ruby, letting the spren escape.


-
+

-
+

I have seen... so much. I have... experienced... annihilation and deconstruction and reconstruction. I have seen the atoms of the world screaming as they were inverted and subverted and demoted to energy. I have seen I have seen I have sget stick bugged lmao

-
+

-
+

-
+

-
+

-
+

-
+

-
+

-
+

-
+

I have uncovered some letters and text not of direct relevance to my art. But, I think I may be able to divine some of the history of the world from these. Let me see...

-
+

-
+

-
+

-
+

-
+

-
+

-
+

-
+

-
+

It appears I have installed some mods Hexcasting interoperates with! I've detailed them here.

-
+

Finally, if I find myself interested in the lore and stories of this world, I do not think any notes compiled while examining these interoperations should be considered as anything more than light trifles.


-
+

I have discovered methods of changing the size of entities, and querying how much larger or smaller they are than normal.


-

A list of all the patterns I've discovered, as well as what they do.

-
+

"→ entity" means it'll just push an entity. "entity, vector →" means it removes an entity and a vector, and doesn't push anything.

Finally, if I find the little dot marking the stroke order too slow or confusing, I can press Control/Command to display a gradient, where the start of the pattern is darkest and the end is lightest. This works on scrolls and when casting, too!


-
+

-
+

Mind's Reflection (→ entity | null)

Null.

A common sequence of patterns, the so-called "raycast mantra," is Mind's Reflection, Compass Purification, Mind's Reflection, Alidade Purification, Archer's Distillation. Together, they return the vector position of the block I am looking at.


-
+

Architect's Distillation (vector, vector → vector | null)

Archer's Distillation, but instead returns the entity I am looking at. Costs a negligible amount of media.


-
+

-
+

Numerical Reflection (→ number)Abacus. But, it's worth knowing the "proper" way to do things.


-
+

Many mathematical operations function on both numbers and vectors. Such arguments are written as "num|vec".



-
+

Additive Distillation (num|vec, num|vec → num|vec)

dot product.


-
+

Division Dstl. (num|vec, num|vec → num|vec)

cross product.

In the first and second cases, the top of the stack or its components comprise the dividend, and the second-from-the-top or its components are the divisor.

WARNING: Never divide by zero!


-
+

Length Purification (num|vec → number)

vector projection of the top of the stack onto the second-from-the-top.

In the first and second cases, the first argument or its components are the base, and the second argument or its components are the exponent.


-
+

-
+

True Reflection (→ bool)

Null influence to the top of the stack.


-
+

Vector Reflection Zero (→ vector)

-
+
-
+

Jester's Gambit (any, any → any, any)

Fisherman's Gambit, but instead of moving the iota, copies it.


-
+

Bookkeeper's Gambit (many → many)

-
+

Augur's Purification (any → bool)Null, and the empty list become False; everything else becomes True.


-
+

-
+

Entity Purification (vector → entity or null)Null if there isn't one).


-
+

Entity Prfn.: Animal (vector → entity or null)Null if there isn't one).


-
+

Entity Prfn.: Monster (vector → entity or null)Null if there isn't one).


-
+

Entity Prfn.: Item (vector → entity or null)Null if there isn't one).


-
+

Entity Prfn.: Player (vector → entity or null)Null if there isn't one).


-
+

Entity Prfn.: Living (vector → entity or null)Null if there isn't one).


-
+

-
+
-
+

influences that I can use to work with patterns directly.

In short, Consideration lets me add one pattern to the stack, and Introspection and Retrospection let me add a whole list.


-
+

Consideration

Scroll or Slate using Scribe's Gambit, and then perhaps decorating with them.


-
+

IntrospectionIntrospection makes my drawing of patterns act differently, for a time. Until I draw Retrospection, the patterns I draw are saved. Then, when I draw Retrospection, they are added to the stack as a list iota.


-
+

Retrospection

Intro- and Retrospection by drawing a Consideration before them, which will simply add them to the list without affecting which the number of Retrospections I need to return to casting.

If I draw two Considerations in a row while introspecting, it will add a single Consideration to the list.


-
+

Evanition

Scroll hung on the wall can have its pattern read off of it.

However, it seems I am unable to save a reference to another player, only me. I suppose an entity reference is similar to the idea of a True Name; perhaps Nature is helping to keep our Names out of the hands of enemies. If I want a friend to have my Name I can make a Focus for them.


-
+

Scribe's Reflection, but the iota is read out of an entity instead of my other hand.


-
+

Chronicler's Gambit (entity, any →)Scribe's Gambit, but the iota is written to an entity instead of my other hand.

Interestingly enough, it looks like I cannot write my own Name using this spell. I get a sense that I might be endangered if I could.


-
+

Auditor's Reflection, but the readability of an entity is checked instead of my other hand.


-
+

Assessor's Reflection, but the writability of an entity is checked instead of my other hand.


-
+

The Ravenmindravenmind. It holds a single iota, much like a Focus, and begins with Null like the same. It is preserved between iterations of Thoth's Gambit, but only lasts as long as the Hex it's a part of. Once I stop casting, the value will be lost.


-
+

Huginn's Gambit (any →)ravenmind, storing it there until I stop casting the Hex.


-
+

Muninn's Reflection (→ any)

-
+
-
+
-
+
-
+
-
+
-
+
-
+

Combination Distillation.


-
+

Conjunction Distillation ((num, num)|(list, list) → num|list)

-
+

Hermes' Gambit ([pattern] | pattern → many)

Foci.

It also makes the bureaucracy of Nature a "Turing-complete" system, according to one esoteric scroll I found.

However, it seems there's a limit to how many times a Hex can cast itself-- Nature doesn't look kindly on runaway spells!

In addition, with the energies of the patterns occurring without me to guide them, any mishap will cause the remaining actions to become too unstable and immediately unravel.


-
+

Iris' Gambit ([pattern] | pattern → many)

Charon's Gambit exists, this allows you to exit nested Hermes' invocations in a controlled way, where Charon only allows you to exit one.

The "Jump" iota will apparently stay on the stack even after execution is finished... better not think about the implications of that.


-
+

Thoth's Gambit (list of patterns, list → list)

Hermes' or Thoth's Gambits, it becomes far more interesting. Those patterns serve to 'contain' that halting, and rather than ending the entire Hex, those gambits end instead. This can be used to cause Thoth's Gambit not to operate on every iota it's given. An escape from the madness, as it were.


-
+

These patterns must be cast from a Spell Circle; trying to cast them through a Staff will fail rather spectacularly.


-
+

Waystone Reflection (→ vector)Impetus of this spell circle.


-
+

Lodestone Reflection (→ vector)Impetus of this spell circle is facing as a unit vector.


-
+

-
+

Akasha's Distillation (vector, pattern → any)Akashic Library with its Record at the given position. This has no range limit. Costs about one Amethyst Dust.


-
+

Akasha's Gambit (vector, pattern, any →)

Patterns and actions that perform a magical effect on the world.

-
+

This way, I can keep a "chooser" item on my hotbar to tell the spell what to use, and fill the rest of my inventory with that item to keep the spell well-stocked.


-
+

-
+

Explosion (vector, number →)

Amethyst Dust per point of explosion power.


-
+

Fireball (vector, number →)

Amethyst Dust, plus about 3 extra Amethyst Dusts per point of explosion power. Otherwise, the same as Explosion, except with fire.


-
+

Impulse (entity, vector →)Amethyst Dust equal to the square of the length of the vector, plus one for every Impulse except the first targeting an entity.


-

The spells catalogued here are purported to be of legendary difficulty and power. They seem to have been recorded only sparsely (for good reason, the texts claim). It's probably just the ramblings of extinct traditionalists, though -- a pattern's a pattern.

What could possibly go wrong?

-
+

-
+

This family of spells all impart a positive potion effect upon an entity, similar to the Nadirs. However, these have their media costs increase with the cube of the potency.


-
+

White Sun's Zenith (entity, number, number →)Amethyst Dust per second.


-
+

Blue Sun's Zenith (entity, number →)Amethyst Dust per 5 seconds.


-
+

Black Sun's Zenith (entity, number, number →)Amethyst Dust per second.


-
+

Red Sun's Zenith (entity, number, number →)Amethyst Dust per 3 seconds.


-
+

Green Sun's Zenith (entity, number, number →)

-
+
-
+
-
+

Dispel Rain

-
+ -
+

-
+

Greater Teleport (entity, vector →)Charged Amethyst.

The transference is not perfect, and it seems when teleporting something as complex as a player, their inventory doesn't quite stay attached, and tends to splatter everywhere at the destination. In addition, the target will be forcibly removed from anything inanimate they are riding or sitting on ... but I've read scraps that suggest animals can come along for the ride, so to speak.


-
+

-
+

Summon Greater Sentinel (vector →)sentinel acts like the normal one I can summon without the use of a Great Spell, if a little more visually interesting. However, the range in which my spells can work is extended to a small region around my greater sentinel, about 16 blocks. In other words, no matter where in the world I am, I can interact with things around my sentinel (the mysterious forces of chunkloading notwithstanding).


-
+

-
+

Craft Phial (entity →)Crafting Casting Items, I must hold a Glass Bottle in my other hand, and provide the spell with a dropped stack of Amethyst. See this page for more information.

Costs about one Charged Amethyst.


-
+

-
+

Flay Mind (entity, vector →)