1<?php
2use dokuwiki\Extension\SyntaxPlugin;
3
4class syntax_plugin_minecraftrecipe extends SyntaxPlugin {
5
6    public function getType() {
7        return 'container';
8    }
9
10    public function getPType() {
11        return 'block';
12    }
13
14    public function getSort() {
15        return 155;
16    }
17
18    public function connectTo($mode) {
19        $this->Lexer->addSpecialPattern('\<recipe(?:\s+type="[^"]*")?\>.*?\</recipe\>', $mode, 'plugin_minecraftrecipe');
20    }
21
22    public function handle($match, $state, $pos, Doku_Handler $handler) {
23        $type = 'crafting'; // default type
24        if(preg_match('/type="([^"]*)"/', $match, $matches)) {
25            $type = $matches[1];
26        }
27
28        $content = preg_replace('/^\<recipe(?:\s+type="[^"]*")?\>|\<\/recipe\>$/i', '', $match);
29        $lines = explode("\n", trim($content));
30
31        switch($type) {
32            case 'smelting':
33                return $this->_handleSmelting($lines);
34            case 'brewing':
35                return $this->_handleBrewing($lines);
36            case 'crafting':
37            default:
38                return $this->_handleCrafting($lines);
39        }
40    }
41
42    protected function _handleCrafting($lines) {
43        $recipe = array();
44        $result = array();
45
46        foreach($lines as $line) {
47            $line = trim($line);
48            if(empty($line)) continue;
49
50            if(strpos($line, '->') !== false) {
51                $resultItems = explode(',', trim(substr($line, 2)));
52                $result = array_map('trim', $resultItems);
53            } else {
54                $slots = array_filter(explode(' ', $line));
55                $row = array();
56                foreach($slots as $slot) {
57                    $items = array_map('trim', explode(',', $slot));
58                    $row[] = $items;
59                }
60                while(count($row) < 3) {
61                    $row[] = array('empty');
62                }
63                if(!empty($row)) {
64                    $recipe[] = $row;
65                }
66            }
67        }
68
69        // pad to 3 rows with empty slots
70        while(count($recipe) < 3) {
71            $recipe[] = array(array('empty'), array('empty'), array('empty'));
72        }
73
74        return array(
75            'type' => 'crafting',
76            'recipe' => $recipe,
77            'result' => $result,
78            'shapeless' => false
79        );
80    }
81
82    protected function _handleSmelting($lines) {
83        $input = array();
84        $fuel = array();
85        $result = array();
86
87        foreach($lines as $index => $line) {
88            $line = trim($line);
89            if(empty($line)) continue;
90
91            if(strpos($line, '->') !== false) {
92                $resultItems = explode(',', trim(substr($line, 2)));
93                $result = array_map('trim', $resultItems);
94            } else if($index === 1) {  // Second line is fuel
95                $fuelItems = explode(',', $line);
96                $fuel = array_map('trim', $fuelItems);
97            } else if($index === 0) {  // First line is input
98                $inputItems = explode(',', $line);
99                $input = array_map('trim', $inputItems);
100            }
101        }
102
103        if(empty($fuel)) {
104            $fuel = array('empty');
105        }
106
107        return array(
108            'type' => 'smelting',
109            'input' => $input,
110            'fuel' => $fuel,
111            'result' => $result
112        );
113    }
114
115    protected function _handleBrewing($lines) {
116        $reagent = array();
117        $potions = array();
118
119        foreach($lines as $line) {
120            $line = trim($line);
121            if(empty($line)) continue;
122
123            if(strpos($line, '->') !== false) {
124                $potionItems = explode(' ', trim(substr($line, 2)));
125                foreach($potionItems as $potion) {
126                    $potions[] = array_map('trim', explode(',', $potion));
127                }
128            } else {
129                // first line is reagent
130                $reagentItems = explode(',', $line);
131                $reagent = array_map('trim', $reagentItems);
132            }
133        }
134
135        while(count($potions) < 3) {
136            $potions[] = array('empty');
137        }
138
139        return array(
140            'type' => 'brewing',
141            'reagent' => $reagent,
142            'potions' => $potions
143        );
144    }
145
146    protected function _renderBrewing($renderer, $data) {
147        $renderer->doc .= '<div class="minecraft-recipe brewing">';
148        $renderer->doc .= '<div class="brewing-layout">';
149
150        $renderer->doc .= '<div class="bubbles"></div>';
151        $renderer->doc .= '<div class="vertical-arrow"></div>';
152        $renderer->doc .= '<div class="pipes"></div>';
153
154        // reagent slot
155        $renderer->doc .= '<div class="reagent grid">';
156        $renderer->doc .= $this->_renderItem($data['reagent']);
157        $renderer->doc .= '</div>';
158
159        // potion slots
160        foreach($data['potions'] as $index => $potion) {
161            $renderer->doc .= '<div class="grid potion-' . ($index + 1) . '">';
162            $renderer->doc .= $this->_renderItem($potion);
163            $renderer->doc .= '</div>';
164        }
165
166        $renderer->doc .= '</div></div>';
167        return true;
168    }
169
170    public function render($mode, Doku_Renderer $renderer, $data) {
171        if($mode != 'xhtml') return false;
172        if(!is_array($data)) return false;
173
174        switch($data['type']) {
175            case 'smelting':
176                return $this->_renderSmelting($renderer, $data);
177            case 'brewing':
178                return $this->_renderBrewing($renderer, $data);
179            case 'crafting':
180            default:
181                return $this->_renderCrafting($renderer, $data);
182        }
183    }
184
185    protected function _renderCrafting($renderer, $data) {
186        $renderer->doc .= '<div class="minecraft-recipe">';
187        $renderer->doc .= '<div class="crafting">';
188        $renderer->doc .= '<div class="recipe">';
189        $renderer->doc .= '<div class="table-grid">';
190
191        if(isset($data['recipe'])) {
192            foreach($data['recipe'] as $row) {
193                foreach($row as $item) {
194                    $renderer->doc .= '<span class="grid">';
195                    $renderer->doc .= $this->_renderItem($item);
196                    $renderer->doc .= '</span>';
197                }
198            }
199        }
200
201        $renderer->doc .= '</div></div>';
202        $renderer->doc .= '<div class="arrow"></div>';
203        $renderer->doc .= '<div class="crafting-table-output">';
204        $renderer->doc .= '<span class="grid-large">';
205        if(isset($data['result'])) {
206            $renderer->doc .= $this->_renderItem($data['result']);
207        }
208        $renderer->doc .= '</span></div></div></div>';
209
210        return true;
211    }
212
213    protected function _renderSmelting($renderer, $data) {
214        $renderer->doc .= '<div class="minecraft-recipe smelting">';
215        $renderer->doc .= '<div class="smelting-layout">';
216
217        // input
218        $renderer->doc .= '<div class="input"><span class="grid">';
219        $renderer->doc .= $this->_renderItem($data['input']);
220        $renderer->doc .= '</span></div>';
221
222        // fire icon
223        $renderer->doc .= '<div class="fire"></div>';
224
225        // fuel
226        $renderer->doc .= '<div class="fuel"><span class="grid">';
227        $renderer->doc .= $this->_renderItem($data['fuel']);
228        $renderer->doc .= '</span></div>';
229
230        // arrow
231        $renderer->doc .= '<div class="arrow"></div>';
232
233        // result
234        $renderer->doc .= '<div class="output"><span class="grid-large">';
235        $renderer->doc .= $this->_renderItem($data['result']);
236        $renderer->doc .= '</span></div>';
237
238        $renderer->doc .= '</div></div>';
239        return true;
240    }
241
242    protected function _renderItem($items) {
243        if(!is_array($items)) {
244            $items = array($items);
245        }
246
247        $allEmpty = true;
248        foreach($items as $item) {
249            $item = strtolower(trim($item));
250            if($item !== 'empty' && $item !== 'x') {
251                $allEmpty = false;
252                break;
253            }
254        }
255
256        if($allEmpty) return '';
257
258        $output = '<div class="item-cycle">';
259        foreach($items as $index => $item) {
260            $item = trim($item);
261            if(strtolower($item) === 'x') continue;
262            if(strtolower($item) === 'empty') continue;
263
264            $localPath = DOKU_PLUGIN . 'minecraftrecipe/images/item/' . strtolower($item) . '.png';
265
266            if(file_exists($localPath)) {
267                $src = DOKU_BASE . 'lib/plugins/minecraftrecipe/images/item/' . strtolower($item) . '.png';
268            } else {
269                $wikiItem = str_replace(' ', '_', $item);
270                $src = 'https://minecraft.wiki/images/Invicon_' . $wikiItem . '.png'; // fallback to minecraft.wiki
271            }
272
273            $visibility = ($index === 0) ? '' : ' style="display: none;"';
274            $output .= '<img src="'.hsc($src).'" alt="'.hsc($item).'" title="'.hsc($item).'" class="item-icon cycle-item"'.$visibility.' data-cycle-index="'.$index.'" />';
275        }
276        $output .= '</div>';
277
278        return $output;
279    }
280}