Compare commits
8
Commits
3c3055eb1f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
137b8f5eb7 | ||
|
|
3dbabfbf0c | ||
|
|
0213eb1ca3 | ||
|
|
fa7ae5069a | ||
|
|
3be844ffb6 | ||
|
|
bf90f3d4c2 | ||
|
|
ca1a01a06e | ||
|
|
387b3925c6 |
@@ -1,4 +1,9 @@
|
|||||||
{
|
{
|
||||||
|
"ignorePatterns": [
|
||||||
|
"node_modules/",
|
||||||
|
"settings.*.js",
|
||||||
|
"webpack.*.js"
|
||||||
|
],
|
||||||
"plugins": ["@typescript-eslint"],
|
"plugins": ["@typescript-eslint"],
|
||||||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "google"],
|
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "google"],
|
||||||
"parser": "@babel/eslint-parser",
|
"parser": "@babel/eslint-parser",
|
||||||
@@ -16,6 +21,7 @@
|
|||||||
"indent": "off",
|
"indent": "off",
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
"new-cap": "off",
|
"new-cap": "off",
|
||||||
|
"no-debugger": "off",
|
||||||
"@typescript-eslint/no-unused-vars": "off"
|
"@typescript-eslint/no-unused-vars": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Бибилотека первоапрельских розыгрышей для форумов MyBB
|
# Библиотека первоапрельских розыгрышей для форумов MyBB
|
||||||
|
|
||||||
Базовое использование:
|
Базовое использование:
|
||||||
|
|
||||||
@@ -26,6 +26,8 @@
|
|||||||
- `letter_chaos` — **Буквенный хаос**. Все буквы в словах, кроме первой и последней, перемешены.
|
- `letter_chaos` — **Буквенный хаос**. Все буквы в словах, кроме первой и последней, перемешены.
|
||||||
- `cursor` — **Красивые курсоры**. Эффекты, следующие за курсором.
|
- `cursor` — **Красивые курсоры**. Эффекты, следующие за курсором.
|
||||||
- `stupid_title` — **Глупые статусы**. Каждому пользователю даётся новый глупый статус.
|
- `stupid_title` — **Глупые статусы**. Каждому пользователю даётся новый глупый статус.
|
||||||
|
- `inverted_scroll` — **Обратная гравитация**. Скролл в обратную сторону.
|
||||||
|
- `latinization` — **Латинизация**. Все символы на странице или только в постах или еще где-то местами - латиницей вместо кириллицы и наоборот
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -48,9 +50,9 @@ window.AprilJokes.settings = {
|
|||||||
## Настройка шуток
|
## Настройка шуток
|
||||||
|
|
||||||
Настройка шуток происходит при присвоении объекта `window.AprilJokes.settings`.
|
Настройка шуток происходит при присвоении объекта `window.AprilJokes.settings`.
|
||||||
Это присвоедние должно произойти **после** загрузки скрипта.
|
Это присвоение должно произойти **после** загрузки скрипта.
|
||||||
|
|
||||||
Для всех шуток определны 2 общих поля:
|
Для всех шуток определены 2 общих поля:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
window.AprilJokes.settings = {
|
window.AprilJokes.settings = {
|
||||||
@@ -75,7 +77,7 @@ window.AprilJokes.settings = {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Полный список настроек с объяснениями вы можете посмотреть в файле [`settings.example.js`](./settings.example.js). Также представлен файл [`settings.compact.js`](./settings.compact.js), который также содержит все настройки, но без коментариев.
|
Полный список настроек с объяснениями вы можете посмотреть в файле [`settings.example.js`](./settings.example.js). Также представлен файл [`settings.compact.js`](./settings.compact.js), который также содержит все настройки, но без комментариев.
|
||||||
|
|
||||||
При этом, объективно, вставлять весь этот код в HTML-начало может быть "немного" неудобно, поэтому вы можете скачать код (кнопка "Raw" над файлом), изменить его, и загрузить в файлы форума, подключив скрипт таким образом:
|
При этом, объективно, вставлять весь этот код в HTML-начало может быть "немного" неудобно, поэтому вы можете скачать код (кнопка "Raw" над файлом), изменить его, и загрузить в файлы форума, подключив скрипт таким образом:
|
||||||
|
|
||||||
@@ -88,7 +90,7 @@ window.AprilJokes.settings = {
|
|||||||
|
|
||||||
Заметьте, что во всех примерах приведены значения настроек по умолчанию. Вы _можете_ удалять строки, значения по умолчанию которых вас устраивают.
|
Заметьте, что во всех примерах приведены значения настроек по умолчанию. Вы _можете_ удалять строки, значения по умолчанию которых вас устраивают.
|
||||||
|
|
||||||
Если вас интерсует только несколько шуток, вы можете использовать метод `disableAll()`, что бы выключить выполнение всех шуток, а потом включать отдельные шутки через установку настройки `enabled`:
|
Если вас интересует только несколько шуток, вы можете использовать метод `disableAll()`, что бы выключить выполнение всех шуток, а потом включать отдельные шутки через установку настройки `enabled`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
window.AprilJokes.disableAll();
|
window.AprilJokes.disableAll();
|
||||||
@@ -127,7 +129,7 @@ window.AprilJokes.settings = {
|
|||||||
window.AprilJokes.start("mirror", true);
|
window.AprilJokes.start("mirror", true);
|
||||||
```
|
```
|
||||||
|
|
||||||
Так же можете [посмотреть](./src/jokes/mirror.ts#L16) на метод `MirrorJoke.start`, что бы узнать как именно проиходит отражение.
|
Так же можете [посмотреть](./src/jokes/mirror.ts#L16) на метод `MirrorJoke.start`, что бы узнать как именно происходит отражение.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -147,9 +149,9 @@ window.AprilJokes.jokes.cursor.disableAll();
|
|||||||
|
|
||||||
### Настройка `stupid_title` (Глупые статусы)
|
### Настройка `stupid_title` (Глупые статусы)
|
||||||
|
|
||||||
> **Важно**: этот блок может быть чрезвычайно капризым из-за особенностей хранения данных, особенно блок `variants`. Изменяя его, строго следуйте этой инструкции, иначе скрипт может перестать работать ожидаемым образом.
|
> **Важно**: этот блок может быть чрезвычайно капризным из-за особенностей хранения данных, особенно блок `variants`. Изменяя его, строго следуйте этой инструкции, иначе скрипт может перестать работать ожидаемым образом.
|
||||||
|
|
||||||
Данная шутка генерирет статус из случайных слов. За счёт использования псевдослучайного генератора, хеширования и привязки к времени UTC, ожидается что все пользователи одновременно будут видеть один и тот же статус для каждого пользователя, при этом он меняется каждые `change_frequency` минут. Установите это значение равным `0`, что бы статус генерировался при каждом обновлении страницы.
|
Данная шутка генерирует статус из случайных слов. За счёт использования псевдослучайного генератора, хеширования и привязки к времени UTC, ожидается что все пользователи одновременно будут видеть один и тот же статус для каждого пользователя, при этом он меняется каждые `change_frequency` минут. Установите это значение равным `0`, что бы статус генерировался при каждом обновлении страницы.
|
||||||
|
|
||||||
Для настройки списка слов используется функция `BuildStupdiTitleVariant`, которая преобразует список слов во внутренний формат скрипта. Настоятельно рекомендуется использовать именно её, а не записывать параметры напрямую:
|
Для настройки списка слов используется функция `BuildStupdiTitleVariant`, которая преобразует список слов во внутренний формат скрипта. Настоятельно рекомендуется использовать именно её, а не записывать параметры напрямую:
|
||||||
|
|
||||||
@@ -178,12 +180,12 @@ window.AprilJokes.settings = {
|
|||||||
|
|
||||||
Обратите внимание на конструкции вида `{ ...something }` — эта конструкция позволяет перенести все поля нескольких старых объектов в один новый. При этом, если в двух объединяемых объектах есть совпадающие значения, то будет сохранено последнее.
|
Обратите внимание на конструкции вида `{ ...something }` — эта конструкция позволяет перенести все поля нескольких старых объектов в один новый. При этом, если в двух объединяемых объектах есть совпадающие значения, то будет сохранено последнее.
|
||||||
|
|
||||||
Говоря проще: добовляйте ваши настройки _после_ строки вида `...window.AprilJokes.settings.stupid_title.variants.adj`, что бы ваши настройки не были перезаписаны.
|
Говоря проще: добавляйте ваши настройки _после_ строки вида `...window.AprilJokes.settings.stupid_title.variants.adj`, что бы ваши настройки не были перезаписаны.
|
||||||
|
|
||||||
Метод `BuildStupdiTitleVariant` принимает 2 аргумента:
|
Метод `BuildStupdiTitleVariant` принимает 2 аргумента:
|
||||||
|
|
||||||
- `gender` — определённый род для генерации. Используйте `true` для мужского рода, `false` для женского и `null`, если добавляемые варианты могут использоваться в обоих случаях.
|
- `gender` — определённый род для генерации. Используйте `true` для мужского рода, `false` для женского и `null`, если добавляемые варианты могут использоваться в обоих случаях.
|
||||||
- `words` — строка, со списоком вариантов через запятую. В качестве вариантов могут быть слова из русских или английских букв, пробелов или дефисов, то есть можно использовать `"..., почему-то и зачем-то , ..."` и `почему-то и зачем-то` будет воспринят как один вариант. Также варианты можно модифицировать, добавив один или несколько модификаторов:
|
- `words` — строка, со списком вариантов через запятую. В качестве вариантов могут быть слова из русских или английских букв, пробелов или дефисов, то есть можно использовать `"..., почему-то и зачем-то , ..."` и `почему-то и зачем-то` будет воспринят как один вариант. Также варианты можно модифицировать, добавив один или несколько модификаторов:
|
||||||
- `!` отключает использование слова. Этим модификатором можно отключать слова из списка по умолчанию (см `!грубый` в примере выше).
|
- `!` отключает использование слова. Этим модификатором можно отключать слова из списка по умолчанию (см `!грубый` в примере выше).
|
||||||
- `%` помечает слово как редкое. Шанс его использования понижается в 2 раза.
|
- `%` помечает слово как редкое. Шанс его использования понижается в 2 раза.
|
||||||
- `^` помечает слово как частое. Шанс его использования повышается в 2 раза.
|
- `^` помечает слово как частое. Шанс его использования повышается в 2 раза.
|
||||||
|
|||||||
Vendored
+117
-24
File diff suppressed because one or more lines are too long
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Generated
+8
-2
@@ -1,17 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "mybb-april-fools",
|
"name": "mybb-april-fools",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mybb-april-fools",
|
"name": "mybb-april-fools",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"license": "https://forumd.ru/viewtopic.php?id=5751#rule",
|
"license": "https://forumd.ru/viewtopic.php?id=5751#rule",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/eslint-parser": "^7.24.1",
|
"@babel/eslint-parser": "^7.24.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.3.1",
|
"@typescript-eslint/eslint-plugin": "^7.3.1",
|
||||||
"cursor-effects": "^1.0.11",
|
"cursor-effects": "^1.0.11",
|
||||||
|
"ignore-loader": "^0.1.2",
|
||||||
"jquery": "^3.6.4",
|
"jquery": "^3.6.4",
|
||||||
"webpack-utf8-bom": "^1.4.0"
|
"webpack-utf8-bom": "^1.4.0"
|
||||||
},
|
},
|
||||||
@@ -7587,6 +7588,11 @@
|
|||||||
"node": ">= 4"
|
"node": ">= 4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ignore-loader": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ignore-loader/-/ignore-loader-0.1.2.tgz",
|
||||||
|
"integrity": "sha512-yOJQEKrNwoYqrWLS4DcnzM7SEQhRKis5mB+LdKKh4cPmGYlLPR0ozRzHV5jmEk2IxptqJNQA5Cc0gw8Fj12bXA=="
|
||||||
|
},
|
||||||
"node_modules/ignore-walk": {
|
"node_modules/ignore-walk": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-4.0.1.tgz",
|
||||||
|
|||||||
+2
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.1.0",
|
"version": "1.1.2",
|
||||||
"name": "mybb-april-fools",
|
"name": "mybb-april-fools",
|
||||||
"description": "Система первоапрельских розыгрышей для форумов MyBB",
|
"description": "Система первоапрельских розыгрышей для форумов MyBB",
|
||||||
"homepage": "https://forumd.ru/viewtopic.php?id=7372",
|
"homepage": "https://forumd.ru/viewtopic.php?id=7372",
|
||||||
@@ -49,6 +49,7 @@
|
|||||||
"@babel/eslint-parser": "^7.24.1",
|
"@babel/eslint-parser": "^7.24.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.3.1",
|
"@typescript-eslint/eslint-plugin": "^7.3.1",
|
||||||
"cursor-effects": "^1.0.11",
|
"cursor-effects": "^1.0.11",
|
||||||
|
"ignore-loader": "^0.1.2",
|
||||||
"jquery": "^3.6.4",
|
"jquery": "^3.6.4",
|
||||||
"webpack-utf8-bom": "^1.4.0"
|
"webpack-utf8-bom": "^1.4.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ window.AprilJokes.disableAll();
|
|||||||
window.AprilJokes.jokes.cursor.disableAll();
|
window.AprilJokes.jokes.cursor.disableAll();
|
||||||
|
|
||||||
window.AprilJokes.settings = {
|
window.AprilJokes.settings = {
|
||||||
|
core: {
|
||||||
|
enabled: true,
|
||||||
|
chance: 100,
|
||||||
|
},
|
||||||
|
|
||||||
shake: {
|
shake: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
chance: 1,
|
chance: 1,
|
||||||
@@ -157,4 +162,49 @@ window.AprilJokes.settings = {
|
|||||||
p: { ...window.AprilJokes.settings.stupid_title.variants.p },
|
p: { ...window.AprilJokes.settings.stupid_title.variants.p },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
inverted_scroll: {
|
||||||
|
enabled: true,
|
||||||
|
chance: 10,
|
||||||
|
},
|
||||||
|
|
||||||
|
latinization: {
|
||||||
|
enabled: true,
|
||||||
|
chance: 10,
|
||||||
|
selector: '.post-content',
|
||||||
|
ruToEn: {
|
||||||
|
'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd',
|
||||||
|
'е': 'e', 'ё': 'e', 'ж': 'j', 'з': 'z', 'и': 'i',
|
||||||
|
'й': 'j', 'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n',
|
||||||
|
'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't',
|
||||||
|
'у': 'u', 'ф': 'f', 'х': 'h', 'ц': 'c', 'ч': 'ch',
|
||||||
|
'ш': 'sh', 'щ': 'shch', 'ъ': 'ie', 'ы': 'y', 'ь': '',
|
||||||
|
'э': 'e', 'ю': 'u', 'я': 'ya',
|
||||||
|
},
|
||||||
|
enToRu: {
|
||||||
|
'a': 'а', 'b': 'б', 'c': 'с', 'd': 'д', 'e': 'е',
|
||||||
|
'f': 'ф', 'gh': 'ж', 'g': 'г', 'h': 'х', 'i': 'и',
|
||||||
|
'j': 'й', 'k': 'к', 'l': 'л', 'm': 'м', 'n': 'н',
|
||||||
|
'o': 'о', 'p': 'п', 'q': 'кью', 'r': 'р', 's': 'с',
|
||||||
|
't': 'т', 'u': 'ю', 'v': 'в', 'w': 'в', 'x': 'кс',
|
||||||
|
'y': 'ы', 'z': 'з',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
fresh_grass: {
|
||||||
|
enabled: true,
|
||||||
|
|
||||||
|
chance: 100,
|
||||||
|
|
||||||
|
keepDistance: 200,
|
||||||
|
maxVelocity: 500,
|
||||||
|
minVelocity: 20,
|
||||||
|
tickPerSecond: 50,
|
||||||
|
gravity: 20,
|
||||||
|
correctionForce: 0.8,
|
||||||
|
mouseForceMax: 5000,
|
||||||
|
mouseForcePow: 5,
|
||||||
|
mass: 1,
|
||||||
|
collisionElasticity: 0.9,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+127
-7
@@ -14,17 +14,17 @@ window.AprilJokes.settings = {
|
|||||||
*/
|
*/
|
||||||
core: {
|
core: {
|
||||||
/**
|
/**
|
||||||
* Активность шутки.
|
* Активность системы.
|
||||||
* Установите это значение `false`, что бы отключить шутку.
|
* Установите это значение `false`, что бы отключить всю системы.
|
||||||
* Если `true`, то шутка будет обработана как обычно.
|
* Если `true`, то система будет обработана как обычно.
|
||||||
*/
|
*/
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Вероятность срабатывания в процентах.
|
* Вероятность срабатывания всей системы в процентах.
|
||||||
* Если установлено значение `100`, шутка будет срабатывать всегда.
|
* Если установлено значение `100`, система будет срабатывать всегда.
|
||||||
* Если установлено значение `0`, шутка никогда не сработает.
|
* Если установлено значение `0`, система никогда не сработает.
|
||||||
* Лучше отключать шутку, используя enabled = false!
|
* Лучше отключать систему, используя `enabled = false`!
|
||||||
*/
|
*/
|
||||||
chance: 100,
|
chance: 100,
|
||||||
},
|
},
|
||||||
@@ -450,4 +450,124 @@ window.AprilJokes.settings = {
|
|||||||
p: { ...window.AprilJokes.settings.stupid_title.variants.p },
|
p: { ...window.AprilJokes.settings.stupid_title.variants.p },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **Обратная гравитация**
|
||||||
|
*
|
||||||
|
* Скролл в обратную сторону (крутишь вверх, а крутится вниз)
|
||||||
|
*
|
||||||
|
* @author Идея: Alex_63
|
||||||
|
* @author Реализация: Kozhilya
|
||||||
|
*/
|
||||||
|
inverted_scroll: {
|
||||||
|
enabled: true,
|
||||||
|
chance: 10,
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **Латинизация**
|
||||||
|
*
|
||||||
|
* Все символы на странице или только в постах или еще где-то местами -
|
||||||
|
* латиницей вместо кириллицы и наоборот
|
||||||
|
*
|
||||||
|
* @author Идея: Alex_63
|
||||||
|
* @author Kozhilya
|
||||||
|
*/
|
||||||
|
latinization: {
|
||||||
|
enabled: true,
|
||||||
|
chance: 10,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Селектор, в котором может изменяться текст
|
||||||
|
*/
|
||||||
|
selector: '.post-content',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Правила преобразования из кириллицы в латиницу
|
||||||
|
*/
|
||||||
|
ruToEn: {
|
||||||
|
'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd',
|
||||||
|
'е': 'e', 'ё': 'e', 'ж': 'j', 'з': 'z', 'и': 'i',
|
||||||
|
'й': 'j', 'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n',
|
||||||
|
'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't',
|
||||||
|
'у': 'u', 'ф': 'f', 'х': 'h', 'ц': 'c', 'ч': 'ch',
|
||||||
|
'ш': 'sh', 'щ': 'shch', 'ъ': 'ie', 'ы': 'y', 'ь': '',
|
||||||
|
'э': 'e', 'ю': 'u', 'я': 'ya',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Правила преобразования из латиницы в кириллицу
|
||||||
|
*/
|
||||||
|
enToRu: {
|
||||||
|
'a': 'а', 'b': 'б', 'c': 'с', 'd': 'д', 'e': 'е',
|
||||||
|
'f': 'ф', 'gh': 'ж', 'g': 'г', 'h': 'х', 'i': 'и',
|
||||||
|
'j': 'й', 'k': 'к', 'l': 'л', 'm': 'м', 'n': 'н',
|
||||||
|
'o': 'о', 'p': 'п', 'q': 'кью', 'r': 'р', 's': 'с',
|
||||||
|
't': 'т', 'u': 'ю', 'v': 'в', 'w': 'в', 'x': 'кс',
|
||||||
|
'y': 'ы', 'z': 'з',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **Свежая трава**
|
||||||
|
*
|
||||||
|
* Предложение потрогать траву
|
||||||
|
* @author Идея: Magia
|
||||||
|
* @author Kozhilya
|
||||||
|
*/
|
||||||
|
fresh_grass: {
|
||||||
|
enabled: true,
|
||||||
|
|
||||||
|
chance: 100,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Расстояние, на котором трава начнёт убегать
|
||||||
|
*/
|
||||||
|
keepDistance: 200,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Максимальная скорость картинки (в пикселях в секунду)
|
||||||
|
*/
|
||||||
|
maxVelocity: 500,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Минимальная скорость картинки (в пикселях в секунду)
|
||||||
|
*/
|
||||||
|
minVelocity: 20,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Количество отметок времени за секунду
|
||||||
|
*/
|
||||||
|
tickPerSecond: 50,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сила притяжения в центр
|
||||||
|
*/
|
||||||
|
gravity: 20,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сила исправления скорости
|
||||||
|
*/
|
||||||
|
correctionForce: 0.8,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Максимальная сила отталкивания от курсора
|
||||||
|
*/
|
||||||
|
mouseForceMax: 5000,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Степень увеличения силы отталкивания от курсора
|
||||||
|
*/
|
||||||
|
mouseForcePow: 5,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Общая масса картинки
|
||||||
|
*/
|
||||||
|
mass: 1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Доля скорости, сохраняемой при столкновении со стеной
|
||||||
|
*/
|
||||||
|
collisionElasticity: 0.9,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,3 +23,84 @@ export function getTextNodes(
|
|||||||
filter((_, node) => node.nodeType === Node.TEXT_NODE);
|
filter((_, node) => node.nodeType === Node.TEXT_NODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export let debugEnabled = ((scriptElement) => {
|
||||||
|
if (!(scriptElement instanceof HTMLScriptElement)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !/\.min\.js$/.test(scriptElement.src);
|
||||||
|
})(document.currentScript);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отоброзить отладочное сообщение
|
||||||
|
*/
|
||||||
|
export function debug(...data: (string | number | object)[]) {
|
||||||
|
if (!debugEnabled) return;
|
||||||
|
console.log(...data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Включить отладку
|
||||||
|
*/
|
||||||
|
export function enableDebug() {
|
||||||
|
debugEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка сенсорного экрана
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export function isTouchEnabled(): boolean {
|
||||||
|
return 'ontouchstart' in window ||
|
||||||
|
navigator.maxTouchPoints > 0 ||
|
||||||
|
// eslint-disable-next-line
|
||||||
|
((navigator as any).msMaxTouchPoints ?? 0) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка, является ли переменная числом
|
||||||
|
* @param {any} n Проверяемая переменная
|
||||||
|
* @return {boolean} Результат проверки
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line
|
||||||
|
export function isNumber(n: any): boolean {
|
||||||
|
return Number(n) === n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка, является ли переменная целым числом
|
||||||
|
* @param {any} n Проверяемая переменная
|
||||||
|
* @return {boolean} Результат проверки
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line
|
||||||
|
export function isInt(n: any): boolean {
|
||||||
|
return Number(n) === n && n % 1 === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка, является ли переменная вещественным числом
|
||||||
|
* @param {any} n Проверяемая переменная
|
||||||
|
* @return {boolean} Результат проверки
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line
|
||||||
|
export function isFloat(n: any): boolean {
|
||||||
|
return Number(n) === n && n % 1 !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Тег литеральной функции, в которой вещественные числа будут
|
||||||
|
* печататься с 2 точками после запятой
|
||||||
|
* @param {TemplateStringsArray} strings
|
||||||
|
* @param {any[]} values
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line
|
||||||
|
export function d2(strings: TemplateStringsArray, ...values: any[]): string {
|
||||||
|
let result = '';
|
||||||
|
for (let i = 0; i < strings.length; i++) {
|
||||||
|
result += strings[i];
|
||||||
|
if (i < values.length) {
|
||||||
|
result += isFloat(values[i]) ? values[i].toFixed(2) : values[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<div class="april-fools__joke__fresh-grass">
|
||||||
|
<div class="april-fools__joke__fresh-grass__window">
|
||||||
|
<h4>А ты трогал сегодня траву?</h4>
|
||||||
|
|
||||||
|
<img class="april-fools__joke__fresh-grass__image"
|
||||||
|
src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/13/Green_Grass.JPG/1280px-Green_Grass.JPG"
|
||||||
|
alt="Здесь должная быть трава, но что-то сломалось... Притворись, что она здесь есть, хорошо?">
|
||||||
|
|
||||||
|
<small>
|
||||||
|
Изображение травы взято с
|
||||||
|
<a href="https://commons.wikimedia.org/wiki/File:Green_Grass.JPG">WikiMedia</a>
|
||||||
|
под лицензией
|
||||||
|
<a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0 DEED</a>.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -11,15 +11,19 @@
|
|||||||
transition: 0.4s all;
|
transition: 0.4s all;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.april-fools__button:hover {
|
.april-fools__button:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.april-fools__joke-row {
|
.april-fools__joke-row {
|
||||||
border-bottom: 1px solid black;
|
border-bottom: 1px solid black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.april-fools__joke-row:first-of-type {
|
.april-fools__joke-row:first-of-type {
|
||||||
border-top: 1px solid black;
|
border-top: 1px solid black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.april-fools__joke {
|
.april-fools__joke {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
@@ -30,9 +34,11 @@
|
|||||||
gap: 1ch;
|
gap: 1ch;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.april-fools__joke:hover {
|
.april-fools__joke:hover {
|
||||||
text-shadow: 0 0 black;
|
text-shadow: 0 0 black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.april-fools__joke:before {
|
.april-fools__joke:before {
|
||||||
display: block;
|
display: block;
|
||||||
flex: 0 0 2ch;
|
flex: 0 0 2ch;
|
||||||
@@ -40,26 +46,84 @@
|
|||||||
content: "✔";
|
content: "✔";
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.april-fools__joke-input:checked + .april-fools__joke {
|
input.april-fools__joke-input:checked + .april-fools__joke {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.april-fools__joke-input:checked + .april-fools__joke:before {
|
input.april-fools__joke-input:checked + .april-fools__joke:before {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.april-fools__joke .april-fools__joke-title {
|
.april-fools__joke .april-fools__joke-title {
|
||||||
flex: 1 auto;
|
flex: 1 auto;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.april-fools__joke .april-fools__joke-description {
|
.april-fools__joke .april-fools__joke-description {
|
||||||
flex: 1 auto;
|
flex: 1 auto;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.april-fools__joke .april-fools__joke-chance {
|
.april-fools__joke .april-fools__joke-chance {
|
||||||
flex: 0 0 4ch;
|
flex: 0 0 4ch;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.april-fools__content .april-fools__reset-button {
|
.april-fools__content .april-fools__reset-button {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 10px auto;
|
margin: 10px auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Шутка "Свежая трава" */
|
||||||
|
html:has(.april-fools__joke__fresh-grass),
|
||||||
|
body:has(.april-fools__joke__fresh-grass) {
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: var(--fresh-grass-body-top);
|
||||||
|
}
|
||||||
|
|
||||||
|
.april-fools__joke__fresh-grass {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #008000C0;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.april-fools__joke__fresh-grass__window {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.april-fools__joke__fresh-grass__window h4 {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
color: #FFFFFF;
|
||||||
|
margin: 1vh 0;
|
||||||
|
font-size: 2vh;
|
||||||
|
line-height: 2vh;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.april-fools__joke__fresh-grass__window small {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.april-fools__joke__fresh-grass__window img {
|
||||||
|
display: block;
|
||||||
|
max-width: 10vw;
|
||||||
|
max-height: 10vh;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+5
-2
@@ -1,6 +1,7 @@
|
|||||||
import './types/mybb.d.ts';
|
import './types/mybb.d';
|
||||||
import {Joke, JokeSettings, StoppableJoke} from './joke';
|
import {Joke, JokeSettings, StoppableJoke} from './joke';
|
||||||
import {JokerUI} from './ui';
|
import {JokerUI} from './ui';
|
||||||
|
import {debug} from './common';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Блок обработки перво-апрельских шуток
|
* Блок обработки перво-апрельских шуток
|
||||||
@@ -13,6 +14,7 @@ export class JokerClass {
|
|||||||
* Общие настройки системы
|
* Общие настройки системы
|
||||||
*/
|
*/
|
||||||
core_settings = new CoreSettings();
|
core_settings = new CoreSettings();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Перечисление всех шуток
|
* Перечисление всех шуток
|
||||||
*/
|
*/
|
||||||
@@ -235,9 +237,10 @@ export class JokerClass {
|
|||||||
|
|
||||||
if (!forced && (!joke.settings.enabled || !this.check(joke.settings.chance))
|
if (!forced && (!joke.settings.enabled || !this.check(joke.settings.chance))
|
||||||
) {
|
) {
|
||||||
|
debug(joke.title, '- шутка не включена');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
debug(joke.title, '- шутка включена');
|
||||||
joke.start();
|
joke.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import {Joke, JokeSettings} from '../joke';
|
||||||
|
import $ from 'jquery';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **Шаблон**
|
||||||
|
*
|
||||||
|
* Описание
|
||||||
|
*
|
||||||
|
* @author Kozhilya
|
||||||
|
*/
|
||||||
|
export class TemplateJoke extends Joke<TemplateJokeSettings> {
|
||||||
|
id = '';
|
||||||
|
|
||||||
|
title = '';
|
||||||
|
|
||||||
|
description = '';
|
||||||
|
|
||||||
|
_settings = new TemplateJokeSettings();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запуск шутки
|
||||||
|
*/
|
||||||
|
start(): void {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Класс настроек для шутки ""
|
||||||
|
*/
|
||||||
|
export class TemplateJokeSettings implements JokeSettings {
|
||||||
|
enabled: boolean = true;
|
||||||
|
|
||||||
|
chance: number = 100;
|
||||||
|
}
|
||||||
@@ -0,0 +1,362 @@
|
|||||||
|
import '../types/html.d';
|
||||||
|
import {Joke, JokeSettings} from '../joke';
|
||||||
|
import $ from 'jquery';
|
||||||
|
import containerHTML from '../html/joke-fresh_glass.html';
|
||||||
|
import {Vector} from '../lib/vector';
|
||||||
|
import {debugEnabled} from '../common';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **Свежая трава**
|
||||||
|
*
|
||||||
|
* Предложение потрогать траву
|
||||||
|
* @author Идея: Magia
|
||||||
|
* @author Kozhilya
|
||||||
|
*/
|
||||||
|
export class FreshGrassJoke extends Joke<FreshGrassJokeSettings> {
|
||||||
|
id = 'fresh_grass';
|
||||||
|
|
||||||
|
title = 'Свежая трава';
|
||||||
|
|
||||||
|
description = 'Предложение потрогать траву';
|
||||||
|
|
||||||
|
_settings = new FreshGrassJokeSettings();
|
||||||
|
|
||||||
|
container: JQuery<HTMLDivElement>;
|
||||||
|
|
||||||
|
intervalIndex: NodeJS.Timeout;
|
||||||
|
|
||||||
|
movement: Movement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запуск шутки
|
||||||
|
*/
|
||||||
|
start(): void {
|
||||||
|
$(() => this.init());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Основная загрузка шутки
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
const $body = $(document.body);
|
||||||
|
|
||||||
|
document.body.style.setProperty(
|
||||||
|
'--fresh-grass-body-top',
|
||||||
|
`-${window.scrollY}px`,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.container = $(containerHTML);
|
||||||
|
this.container.hide(0);
|
||||||
|
$body.append(this.container);
|
||||||
|
this.container.fadeIn(500);
|
||||||
|
|
||||||
|
this.fleeingGrass();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отладка
|
||||||
|
* @param {string} str
|
||||||
|
*/
|
||||||
|
debug(str: string): void {
|
||||||
|
if (!debugEnabled) return;
|
||||||
|
|
||||||
|
$('small', this.container).html(`<pre>${str}</pre>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Анимация убегания травы от пользователя
|
||||||
|
*/
|
||||||
|
fleeingGrass(): void {
|
||||||
|
this.movement = new Movement(this.settings);
|
||||||
|
|
||||||
|
const $img = $('img.april-fools__joke__fresh-grass__image', this.container);
|
||||||
|
|
||||||
|
$img.on('mousedown', () => {
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(window).on('mousemove', (e) => {
|
||||||
|
this.movement.mouse = new Vector(e.clientX - window.innerWidth / 2,
|
||||||
|
e.clientY - window.innerHeight / 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.intervalIndex = setInterval(() => {
|
||||||
|
const img = $img[0].getBoundingClientRect();
|
||||||
|
this.movement.step(img);
|
||||||
|
|
||||||
|
// this.debug(this.movement.debugLines.join('\n'));
|
||||||
|
|
||||||
|
$img.css('transform', this.movement.state.position.toTransform());
|
||||||
|
}, Math.floor(1000 * this.movement.dt));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Закрыть окно
|
||||||
|
*/
|
||||||
|
close(): void {
|
||||||
|
clearInterval(this.intervalIndex);
|
||||||
|
|
||||||
|
this.container.fadeOut({
|
||||||
|
duration: 500,
|
||||||
|
// eslint-disable-next-line
|
||||||
|
// @ts-ignore
|
||||||
|
complete: () => {
|
||||||
|
this.container.remove();
|
||||||
|
|
||||||
|
scrollTo(0,
|
||||||
|
-parseInt(document.body.style.getPropertyValue(
|
||||||
|
'--fresh-grass-body-top')));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MovementData = {
|
||||||
|
position: Vector;
|
||||||
|
velocity: Vector;
|
||||||
|
acceleration: Vector;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Класс обработки движения
|
||||||
|
*/
|
||||||
|
class Movement {
|
||||||
|
settings: FreshGrassJokeSettings;
|
||||||
|
state: MovementData;
|
||||||
|
mouse: Vector;
|
||||||
|
dt: number;
|
||||||
|
last: MovementData;
|
||||||
|
debugLines: string[];
|
||||||
|
forces: { [key in string]: Vector };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Конструктор
|
||||||
|
* @param {FreshGrassJokeSettings} settings
|
||||||
|
*/
|
||||||
|
constructor(settings: FreshGrassJokeSettings) {
|
||||||
|
this.settings = settings;
|
||||||
|
this.state = {
|
||||||
|
position: new Vector(),
|
||||||
|
velocity: Vector.fromLengthAngle(Math.random() * 2 * Math.PI,
|
||||||
|
Math.random() *
|
||||||
|
(this.settings.maxVelocity - this.settings.minVelocity) +
|
||||||
|
this.settings.minVelocity),
|
||||||
|
acceleration: new Vector(),
|
||||||
|
};
|
||||||
|
this.mouse = new Vector();
|
||||||
|
this.dt = 1.0 / this.settings.tickPerSecond;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Координата движения
|
||||||
|
*/
|
||||||
|
get position() {
|
||||||
|
return this.state.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Скорость движения
|
||||||
|
*/
|
||||||
|
get velocity() {
|
||||||
|
return this.state.velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ускорение движения
|
||||||
|
*/
|
||||||
|
get acceleration() {
|
||||||
|
return this.state.acceleration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Произвести вычисление
|
||||||
|
* @param {DOMRect} img Изображение
|
||||||
|
*/
|
||||||
|
step(img: DOMRect) {
|
||||||
|
this.last = this.state;
|
||||||
|
this.debugLines = [];
|
||||||
|
this.forces = {};
|
||||||
|
|
||||||
|
// Обработка сил
|
||||||
|
this.processForces();
|
||||||
|
this.processMovement();
|
||||||
|
this.processCollisions(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обработка сил
|
||||||
|
*/
|
||||||
|
processForces() {
|
||||||
|
// Притяжение к центру
|
||||||
|
this.forces.gravity = Vector.fromLengthAngle(
|
||||||
|
this.last.position.angle + Math.PI,
|
||||||
|
this.settings.gravity,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Коррекция скорости
|
||||||
|
if (this.last.velocity.length < this.settings.minVelocity) {
|
||||||
|
this.forces.correction = Vector.fromLengthAngle(
|
||||||
|
this.last.velocity.angle,
|
||||||
|
this.settings.correctionForce *
|
||||||
|
Math.pow(this.last.velocity.length - this.settings.minVelocity, 2),
|
||||||
|
);
|
||||||
|
} else if (this.last.velocity.length > this.settings.maxVelocity) {
|
||||||
|
this.forces.correction = Vector.fromLengthAngle(
|
||||||
|
this.last.velocity.angle + Math.PI,
|
||||||
|
this.settings.correctionForce *
|
||||||
|
Math.pow(this.last.velocity.length - this.settings.maxVelocity, 2),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отталкивание от мыши
|
||||||
|
const toMouseVector = this.mouse.add(this.last.position.reverse());
|
||||||
|
if (toMouseVector.length < this.settings.keepDistance &&
|
||||||
|
toMouseVector.length > 0) {
|
||||||
|
this.forces.running = Vector.fromLengthAngle(
|
||||||
|
toMouseVector.angle + Math.PI,
|
||||||
|
this.settings.mouseForceMax -
|
||||||
|
Math.pow(
|
||||||
|
Math.abs(toMouseVector.length / this.settings.keepDistance - 1),
|
||||||
|
this.settings.mouseForcePow),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обработка движения
|
||||||
|
*/
|
||||||
|
processMovement() {
|
||||||
|
// Ускорение
|
||||||
|
this.debugLines.push('Forces:');
|
||||||
|
let acceleration: Vector = new Vector();
|
||||||
|
|
||||||
|
for (const [name, force] of Object.entries(this.forces)) {
|
||||||
|
acceleration = acceleration.add(force);
|
||||||
|
this.debugLines.push(` [${name}] ${force}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
acceleration = acceleration.multiply(1 / this.settings.mass);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
position: this.last.position,
|
||||||
|
velocity: this.last.velocity,
|
||||||
|
acceleration: acceleration,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.state.velocity = this.velocity.add(
|
||||||
|
this.acceleration.multiply(this.dt));
|
||||||
|
this.state.position = this.position.add(
|
||||||
|
this.velocity.multiply(this.dt));
|
||||||
|
|
||||||
|
this.debugLines.push(`acceleration: ${this.acceleration}`);
|
||||||
|
this.debugLines.push(`velocity: ${this.velocity}`);
|
||||||
|
this.debugLines.push(`position: ${this.position}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обработка столкновения с границами окна
|
||||||
|
* @param {DOMRect} img Изображение
|
||||||
|
*/
|
||||||
|
processCollisions(img: DOMRect) {
|
||||||
|
const limitX = (window.innerWidth - img.width) / 2;
|
||||||
|
const limitY = (window.innerHeight - img.height) / 2;
|
||||||
|
|
||||||
|
if (this.position.x < -limitX) {
|
||||||
|
// Left
|
||||||
|
this.position.x = -2 * limitX - this.position.x;
|
||||||
|
this.velocity.x = -this.velocity.x * this.settings.collisionElasticity;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.position.x > limitX) {
|
||||||
|
// Right
|
||||||
|
this.position.x = 2 * limitX - this.position.x;
|
||||||
|
this.velocity.x = -this.velocity.x * this.settings.collisionElasticity;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.position.y < -limitY) {
|
||||||
|
// Top
|
||||||
|
this.position.y = -2 * limitY - this.position.y;
|
||||||
|
this.velocity.y = -this.velocity.y * this.settings.collisionElasticity;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.position.y > limitY) {
|
||||||
|
// Bottom
|
||||||
|
this.position.y = 2 * limitY - this.position.y;
|
||||||
|
this.velocity.y = -this.velocity.y * this.settings.collisionElasticity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Нормально распределённая случайная величина
|
||||||
|
* @param {number} mean
|
||||||
|
* @param {number} stdev
|
||||||
|
* @return {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private randomGaussian(mean: number = 0, stdev: number = 1): number {
|
||||||
|
const u = 1 - Math.random(); // Converting [0,1) to (0,1]
|
||||||
|
const v = Math.random();
|
||||||
|
const z = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
|
||||||
|
// Transform to the desired mean and standard deviation:
|
||||||
|
return z * stdev + mean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Класс настроек для шутки "Свежая трава"
|
||||||
|
*/
|
||||||
|
export class FreshGrassJokeSettings implements JokeSettings {
|
||||||
|
enabled: boolean = true;
|
||||||
|
|
||||||
|
chance: number = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Расстояние, на котором трава начнёт убегать
|
||||||
|
*/
|
||||||
|
keepDistance: number = 200;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Максимальная скорость картинки (в пикселях в секунду)
|
||||||
|
*/
|
||||||
|
maxVelocity: number = 500;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Минимальная скорость картинки (в пикселях в секунду)
|
||||||
|
*/
|
||||||
|
minVelocity: number = 20;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Количество отметок времени за секунду
|
||||||
|
*/
|
||||||
|
tickPerSecond: number = 50;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сила притяжения в центр
|
||||||
|
*/
|
||||||
|
gravity: number = 20;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сила исправления скорости
|
||||||
|
*/
|
||||||
|
correctionForce: number = 0.8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Максимальная сила отталкивания от курсора
|
||||||
|
*/
|
||||||
|
mouseForceMax: number = 5000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Степень увеличения силы отталкивания от курсора
|
||||||
|
*/
|
||||||
|
mouseForcePow: number = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Общая масса картинки
|
||||||
|
*/
|
||||||
|
mass: number = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Доля скорости, сохраняемой при столкновении со стеной
|
||||||
|
*/
|
||||||
|
collisionElasticity: number = 0.9;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import {Joke, JokeSettings} from '../joke';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **Обратная гравитация**
|
||||||
|
*
|
||||||
|
* Скролл в обратную сторону (крутишь вверх, а крутится вниз)
|
||||||
|
*
|
||||||
|
* @author Идея: Alex_63
|
||||||
|
* @author Реализация: Kozhilya
|
||||||
|
*/
|
||||||
|
export class InvertedScrollJoke extends Joke<InvertedScrollJokeSettings> {
|
||||||
|
id = 'inverted_scroll';
|
||||||
|
|
||||||
|
title = 'Обратная гравитация';
|
||||||
|
|
||||||
|
description = 'Скролл в обратную сторону';
|
||||||
|
|
||||||
|
_settings = new InvertedScrollJokeSettings();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запуск шутки
|
||||||
|
*/
|
||||||
|
start(): void {
|
||||||
|
// let lastScroll = window.scrollY;
|
||||||
|
|
||||||
|
document.addEventListener('wheel', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
scrollBy(0, -e.deltaY);
|
||||||
|
}, {passive: false});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Класс настроек для шутки "Обратная гравитация"
|
||||||
|
*/
|
||||||
|
export class InvertedScrollJokeSettings implements JokeSettings {
|
||||||
|
enabled: boolean = true;
|
||||||
|
|
||||||
|
chance: number = 10;
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import {Joke, JokeSettings} from '../joke';
|
||||||
|
import {getTextNodes} from '../common';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **Латинизация**
|
||||||
|
*
|
||||||
|
* Все символы на странице или только в постах или еще где-то местами -
|
||||||
|
* латиницей вместо кириллицы и наоборот
|
||||||
|
*
|
||||||
|
* @author Идея: Alex_63
|
||||||
|
* @author Kozhilya
|
||||||
|
*/
|
||||||
|
export class LatinizationJoke extends Joke<LatinizationJokeSettings> {
|
||||||
|
id = 'latinization';
|
||||||
|
|
||||||
|
title = 'Латинизация';
|
||||||
|
|
||||||
|
description = 'Все символы на странице или только в постах ' +
|
||||||
|
'или еще где-то местами - латиницей вместо кириллицы и наоборот';
|
||||||
|
|
||||||
|
_settings = new LatinizationJokeSettings();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запуск шутки
|
||||||
|
*/
|
||||||
|
start(): void {
|
||||||
|
const textNodes = getTextNodes(this.settings.selector);
|
||||||
|
|
||||||
|
textNodes.each((_, node) => this.processNode(node as Text));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Латинизация или кириллизация слова
|
||||||
|
* @param {string} word
|
||||||
|
* @return {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private latinize(word: string): string {
|
||||||
|
if (/[а-яА-ЯёЁ0-9_-]+/.test(word)) {
|
||||||
|
// Кириллица в латиницу
|
||||||
|
for (const [ru, en] of Object.entries(this.settings.ruToEn)) {
|
||||||
|
word = word.replace(new RegExp(ru, 'g'), en).
|
||||||
|
replace(new RegExp(ru.toUpperCase(), 'gi'), en.toUpperCase());
|
||||||
|
}
|
||||||
|
} else if (/[a-zA-Z0-9_-]+/.test(word)) {
|
||||||
|
// Латиница в кириллицу
|
||||||
|
for (const [en, ru] of Object.entries(this.settings.enToRu)) {
|
||||||
|
word = word.replace(new RegExp(en, 'g'), ru).
|
||||||
|
replace(new RegExp(en.toUpperCase(), 'gi'), ru.toUpperCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обработка текстовой ноды
|
||||||
|
* @param {Text} node
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private processNode(node: Text) {
|
||||||
|
node.textContent = node.textContent?.replace(
|
||||||
|
/([А-Яа-яA-Za-z]+)/gm,
|
||||||
|
(_: string, word: string) => this.latinize(word),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Класс настроек для шутки "Латинизация"
|
||||||
|
*/
|
||||||
|
export class LatinizationJokeSettings implements JokeSettings {
|
||||||
|
enabled: boolean = true;
|
||||||
|
|
||||||
|
chance: number = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Селектор, в котором может изменяться текст
|
||||||
|
*/
|
||||||
|
selector: string = '.post-content';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Правила преобразования из кириллицы в латиницу
|
||||||
|
*/
|
||||||
|
ruToEn: { [key in string]: string } = {
|
||||||
|
'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd',
|
||||||
|
'е': 'e', 'ё': 'e', 'ж': 'j', 'з': 'z', 'и': 'i',
|
||||||
|
'й': 'j', 'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n',
|
||||||
|
'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't',
|
||||||
|
'у': 'u', 'ф': 'f', 'х': 'h', 'ц': 'c', 'ч': 'ch',
|
||||||
|
'ш': 'sh', 'щ': 'shch', 'ъ': 'ie', 'ы': 'y', 'ь': '',
|
||||||
|
'э': 'e', 'ю': 'u', 'я': 'ya',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Правила преобразования из латиницы в кириллицу
|
||||||
|
*/
|
||||||
|
enToRu: { [key in string]: string } = {
|
||||||
|
'a': 'а', 'b': 'б', 'c': 'с', 'd': 'д', 'e': 'е',
|
||||||
|
'f': 'ф', 'gh': 'ж', 'g': 'г', 'h': 'х', 'i': 'и',
|
||||||
|
'j': 'й', 'k': 'к', 'l': 'л', 'm': 'м', 'n': 'н',
|
||||||
|
'o': 'о', 'p': 'п', 'q': 'кью', 'r': 'р', 's': 'с',
|
||||||
|
't': 'т', 'u': 'ю', 'v': 'в', 'w': 'в', 'x': 'кс',
|
||||||
|
'y': 'ы', 'z': 'з',
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
import {d2} from '../common';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Класс вектора
|
||||||
|
*/
|
||||||
|
export class Vector {
|
||||||
|
/**
|
||||||
|
* Координата X
|
||||||
|
*/
|
||||||
|
x: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Координата Y
|
||||||
|
*/
|
||||||
|
y: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Конструктор вектора
|
||||||
|
* @param {number} x Координата X
|
||||||
|
* @param {number} y Координата Y
|
||||||
|
*/
|
||||||
|
constructor(x: number = 0, y: number = 0) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Угол наклона вектора
|
||||||
|
*/
|
||||||
|
get angle() {
|
||||||
|
return Math.atan2(this.y, this.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Угол наклона вектора
|
||||||
|
* @param {number} angle
|
||||||
|
*/
|
||||||
|
set angle(angle: number) {
|
||||||
|
const length = this.length;
|
||||||
|
this.x = Math.cos(angle) * length;
|
||||||
|
this.y = Math.sin(angle) * length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Длина вектора
|
||||||
|
*/
|
||||||
|
get length() {
|
||||||
|
return Math.distance(this.x, this.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Длина вектора
|
||||||
|
* @param {number} length
|
||||||
|
*/
|
||||||
|
set length(length: number) {
|
||||||
|
const angle = this.angle;
|
||||||
|
this.x = Math.cos(angle) * length;
|
||||||
|
this.y = Math.sin(angle) * length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сумма векторов
|
||||||
|
* @param {Vector} a Первый вектор
|
||||||
|
* @param {Vector} b Второй вектор
|
||||||
|
* @return {Vector} Вектор суммы
|
||||||
|
*/
|
||||||
|
static addVectors(a: Vector, b: Vector): Vector {
|
||||||
|
return new Vector(a.x + b.x, a.y + b.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Скалярное умножение векторов
|
||||||
|
* @param {Vector} a Первый вектор
|
||||||
|
* @param {Vector} b Второй вектор
|
||||||
|
* @return {number} Скалярное произведение векторов
|
||||||
|
*/
|
||||||
|
static dotProduct(a: Vector, b: Vector): number {
|
||||||
|
return a.x * b.x + a.y * b.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Создать вектор по углу наклона и длине
|
||||||
|
* @param {number} angle Угол наклона
|
||||||
|
* @param {number} length Длина
|
||||||
|
* @return {Vector} Вектор суммы
|
||||||
|
*/
|
||||||
|
static fromLengthAngle(angle: number, length: number): Vector {
|
||||||
|
const v = new Vector();
|
||||||
|
v.length = length;
|
||||||
|
v.angle = angle;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Нахождение противоположного вектора
|
||||||
|
* @return {Vector} Противоположный вектор
|
||||||
|
*/
|
||||||
|
reverse(): Vector {
|
||||||
|
return new Vector(-this.x, -this.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Прибавить вектор
|
||||||
|
* @param {Vector} a Второе слагаемое
|
||||||
|
* @return {Vector} Вектор суммы
|
||||||
|
*/
|
||||||
|
add(a: Vector): Vector {
|
||||||
|
return Vector.addVectors(this, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Умножение вектора на число
|
||||||
|
* @param {number} a Скаляр
|
||||||
|
* @return {Vector} Вектор-результат
|
||||||
|
*/
|
||||||
|
multiply(a: number): Vector {
|
||||||
|
return new Vector(a * this.x, a * this.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Преобразование в CSS-преобразование
|
||||||
|
* @return {string} CSS-преобразование
|
||||||
|
*/
|
||||||
|
toTransform(): string {
|
||||||
|
return `translate(${this.x}px, ${this.y}px)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Строковое представление
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
toString(): string {
|
||||||
|
return d2`Vector(x=${this.x}, y=${this.y}, ` +
|
||||||
|
d2`length=${this.length}, angle=${this.angle})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Math {
|
||||||
|
/**
|
||||||
|
* Расчёт длины вектора
|
||||||
|
* @param {number} x Координата X вектора
|
||||||
|
* @param {number} y Координата X вектора
|
||||||
|
* @return {number} Длина вектора
|
||||||
|
*/
|
||||||
|
distance(x: number, y: number): number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Math.distance = (x, y) => Math.sqrt(x * x + y * y);
|
||||||
@@ -8,6 +8,9 @@ import {CrazyLettersJoke} from './jokes/crazy_letters';
|
|||||||
import {LetterChaosJoke} from './jokes/letter_chaos';
|
import {LetterChaosJoke} from './jokes/letter_chaos';
|
||||||
import {CursorEffectsJoke} from './jokes/cursor';
|
import {CursorEffectsJoke} from './jokes/cursor';
|
||||||
import {StupidTitleJoke} from './jokes/stupid_title';
|
import {StupidTitleJoke} from './jokes/stupid_title';
|
||||||
|
import {InvertedScrollJoke} from './jokes/inverted_scroll';
|
||||||
|
import {LatinizationJoke} from './jokes/latinization';
|
||||||
|
import {FreshGrassJoke} from './jokes/fresh_grass';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window { AprilJokes: JokerClass; }
|
interface Window { AprilJokes: JokerClass; }
|
||||||
@@ -32,6 +35,9 @@ window.AprilJokes = ((jokes: JokeList): JokerClass => {
|
|||||||
LetterChaosJoke,
|
LetterChaosJoke,
|
||||||
CursorEffectsJoke,
|
CursorEffectsJoke,
|
||||||
StupidTitleJoke,
|
StupidTitleJoke,
|
||||||
|
InvertedScrollJoke,
|
||||||
|
LatinizationJoke,
|
||||||
|
FreshGrassJoke,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
interface JokeList extends Array<new () => Joke<JokeSettings>> {}
|
interface JokeList extends Array<new () => Joke<JokeSettings>> {}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
// / <reference path="html.d.ts"/>
|
import './types/html.d';
|
||||||
|
import './types/mybb.d';
|
||||||
import './types/html.d.ts';
|
|
||||||
import './types/mybb.d.ts';
|
|
||||||
|
|
||||||
import {JokerClass} from './joker';
|
import {JokerClass} from './joker';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
|||||||
+14
-3
@@ -3,8 +3,9 @@ const webpack = require("webpack");
|
|||||||
const BomPlugin = require("webpack-utf8-bom");
|
const BomPlugin = require("webpack-utf8-bom");
|
||||||
const TerserPlugin = require("terser-webpack-plugin");
|
const TerserPlugin = require("terser-webpack-plugin");
|
||||||
const { libBanner } = require("./webpack.banner");
|
const { libBanner } = require("./webpack.banner");
|
||||||
|
const { libUpload } = require("./webpack.upload");
|
||||||
|
|
||||||
const isProduction = process.env.NODE_ENV == "production";
|
const isProduction = process.env.NODE_ENV === "production";
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
entry: "./src/main.ts",
|
entry: "./src/main.ts",
|
||||||
@@ -20,9 +21,19 @@ const config = {
|
|||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.(ts|tsx)$/i,
|
test: /\.tsx?$/i,
|
||||||
loader: "ts-loader",
|
loader: "ts-loader",
|
||||||
exclude: ["/node_modules/"],
|
exclude: /node_modules|\.d\.ts$/,
|
||||||
|
// exclude: ["/node_modules/|.d.ts"],
|
||||||
|
options: {
|
||||||
|
compilerOptions: {
|
||||||
|
noEmit: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.d\.ts$/i,
|
||||||
|
loader: 'ignore-loader'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
|
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
|
||||||
|
|||||||
+5
-5
@@ -1,16 +1,16 @@
|
|||||||
|
|
||||||
const { exec } = require("node:child_process");
|
const { exec } = require('node:child_process');
|
||||||
|
|
||||||
const libUpload = {
|
const libUpload = {
|
||||||
apply: (compiler) => {
|
apply: (compiler) => {
|
||||||
compiler.hooks.afterEmit.tap("AfterEmitPlugin", (compilation) => {
|
compiler.hooks.afterEmit.tap('AfterEmitPlugin', (compilation) => {
|
||||||
exec("./upload.sh", (err, stdout, stderr) => {
|
exec('./upload.sh', (err, stdout, stderr) => {
|
||||||
if (stdout) process.stdout.write(stdout);
|
if (stdout) process.stdout.write(stdout);
|
||||||
if (stderr) process.stderr.write(stderr);
|
if (stderr) process.stderr.write(stderr);
|
||||||
console.log("Uploaded.");
|
console.log('Uploaded.');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports
|
module.exports = { libUpload };
|
||||||
Reference in New Issue
Block a user