Compare commits

...

299 commits

Author SHA1 Message Date
Ell ede5f3d30d added back ff
All checks were successful
/ web (push) Successful in 36s
2024-04-01 14:39:29 +02:00
Ell 1c1ee128e3 more more quotes
All checks were successful
/ web (push) Successful in 1m11s
2024-03-14 22:39:03 +01:00
Ell 462b043080 time for more quotes
All checks were successful
/ web (push) Successful in 1m5s
2024-03-14 20:32:40 +01:00
Ell 61ee0b08f1 happening again!
All checks were successful
/ web (push) Successful in 25s
2024-03-04 22:30:24 +01:00
Ell 84a9544d34 fixed inconsistent styling for reedsy prompt info
All checks were successful
/ web (push) Successful in 36s
2024-02-27 10:32:50 +01:00
Ell 08fd4d1f91 add super simple time tracker to project list
All checks were successful
/ web (push) Successful in 33s
2024-02-26 14:09:05 +01:00
Ell d51bee83ee updated copyright year
All checks were successful
/ web (push) Successful in 1m45s
2024-02-24 22:24:56 +01:00
Ell 4a75dc2784 close commissions more thoroughly
All checks were successful
/ web (push) Successful in 1m8s
2024-02-04 19:57:38 +01:00
Ell 20a783b1ba re-enable cache
All checks were successful
/ web (push) Successful in 2m10s
2024-01-14 11:48:12 +01:00
Ell de81dfb68d let rsync find the files itself
All checks were successful
/ web (push) Successful in 1m7s
2024-01-13 20:43:39 +01:00
Ell b7a45219cd install rsync to deploy
All checks were successful
/ web (push) Successful in 1m12s
2024-01-13 20:29:53 +01:00
Ell 83eb2686ec don't need this environment variable anymore
Some checks failed
/ web (push) Failing after 54s
2024-01-13 20:27:26 +01:00
Ell 40eb0e673f bundle install
Some checks failed
/ web (push) Has been cancelled
2024-01-13 20:27:13 +01:00
Ell c38c88ce80 some fixes
Some checks failed
/ web (push) Failing after 12s
2024-01-13 20:26:07 +01:00
Ell b333cf98a3 also allow building on linux
Some checks failed
/ web (push) Failing after 1m16s
2024-01-13 19:55:42 +01:00
Ell ef775d73cd ruby is a github action
Some checks failed
/ web (push) Failing after 4m43s
2024-01-13 19:01:00 +01:00
Ell e4469b0884 this repo still has an old branch name oh no
Some checks failed
/ web (push) Failing after 2s
2024-01-13 19:00:24 +01:00
Ell a2c27efdeb switch to FJ actions 2024-01-13 18:59:43 +01:00
Ell 2b75f5f5c4 social update
All checks were successful
ci/woodpecker/push/main Pipeline was successful
2023-12-14 11:38:58 +01:00
Ell c297720f89 firm faves
All checks were successful
ci/woodpecker/push/main Pipeline was successful
2023-10-27 13:38:47 +02:00
Ell 22ed9c2a22 update
All checks were successful
ci/woodpecker/push/main Pipeline was successful
i know it's not november yet but almost ok
2023-10-26 11:32:38 +02:00
Ell 83dec577e2 add some more quotes again after a while!!
All checks were successful
ci/woodpecker/push/main Pipeline was successful
2023-10-25 22:26:42 +02:00
Ell 83e4e487f8 moved closed commissions info around
All checks were successful
ci/woodpecker/push/main Pipeline was successful
2023-10-03 22:40:52 +02:00
Ell ce6643d48f removed socials i don't actually use
All checks were successful
ci/woodpecker/push/main Pipeline was successful
2023-09-10 23:40:32 +02:00
Ell 427ecb50fb remove AC:NH from socials
All checks were successful
ci/woodpecker/push/main Pipeline was successful
2023-09-10 23:39:09 +02:00
Ell 19b5955efd small About update
All checks were successful
ci/woodpecker/push/main Pipeline was successful
2023-08-18 17:24:56 +02:00
Ell 812fe57577 cool cleanup club
All checks were successful
ci/woodpecker/push/main Pipeline was successful
2023-08-07 14:04:06 +02:00
Ell 5f248b47d5 Merge remote-tracking branch 'origin/master'
All checks were successful
ci/woodpecker/push/main Pipeline was successful
2023-08-07 13:47:40 +02:00
Ell b92abccf1c my life actually isn't that busy, i'm just depressed 2023-08-07 13:47:33 +02:00
Ell c2714a6bc0 fixed navbar collapse on mobile
All checks were successful
ci/woodpecker/push/main Pipeline was successful
2023-07-09 14:06:52 +02:00
Ell d1de0d218c allow manual invocations to trigger deploy as well
All checks were successful
ci/woodpecker/push/main Pipeline was successful
2023-07-02 19:20:53 +02:00
Ell f26eb35b29 removed jenkinsfile
All checks were successful
ci/woodpecker/push/main Pipeline was successful
2023-07-02 19:16:34 +02:00
Ell d4d53d5b75 added woodpecker build
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
ci/woodpecker/manual/main Pipeline was successful
2023-07-02 19:14:01 +02:00
Ell 29057b7308 update git social
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-06-30 15:39:26 +02:00
Ell 4cac6e45b5 also use landing page links here!
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-06-28 19:30:24 +02:00
Ell b3053002c4 disable ligatures for code
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-06-26 23:09:07 +02:00
Ell 55f51b95b3 use jetbrains mono for code
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-06-25 17:33:51 +02:00
Ell 20601b4dde use new obsidian logo
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-06-23 20:29:25 +02:00
Ell a9b877ace9 oop
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-06-22 23:19:38 +02:00
Ell 7851a46fab project status and blog category style improvements
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-06-22 23:14:08 +02:00
Ell 9b55c6fccd some style improvements
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-06-22 22:17:25 +02:00
Ell dd19c86d18 cleaned up privacy and imprint
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-06-22 13:23:12 +02:00
Ell 2a58a0a1d2 script cleanup
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-06-19 10:58:12 +02:00
Ell db2ad09a31 move scripts above styling
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-06-18 19:37:53 +02:00
Ell a4b5dae3ad blind inline oop
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-06-13 17:36:14 +02:00
Ell 11c00e85b3 switch to local storage
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-06-13 17:29:52 +02:00
Ell 2844b58dda use roboto slab as serif font & for headers
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-06-06 15:11:49 +02:00
Ell 1610c0618c added anchor headings to blog
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-06-02 13:43:25 +02:00
Ell 6f5ec98dff switch to a global charity for pride
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-06-01 09:25:38 +02:00
Ell 1d7b6f3405 quotes
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-05-24 17:11:02 +02:00
Ell 69f610fc99 added social disclaimer, and some styling improvements
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-05-17 10:00:09 +02:00
Ell 41e8e84f57 moved tiny life to the top
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-05-16 14:02:55 +02:00
Ell 4d2b00b30e Merge branch 'master' of https://git.ellpeck.de/Ellpeck/Web
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-05-15 19:56:37 +02:00
Ell 2d9c2e7ce5 updated copyright years 2023-05-15 19:56:34 +02:00
Ell 8ff54673eb already update this
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-05-03 16:44:26 +02:00
Ell a81eb7fcdc we don't need to force to anchor here
All checks were successful
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-04-29 22:48:32 +02:00
Ell a1c0452607 web label
All checks were successful
Web/pipeline/head This commit looks good
Jenkins
Ellpeck/Web/pipeline/head This commit looks good
2023-04-11 14:31:21 +02:00
Ell 3ce650ea86 improved visuals of the support section
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2023-04-09 14:10:20 +02:00
Ell 1df295e264 updated deps
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2023-04-08 21:21:40 +02:00
Ell 2b87e29b43 added back touchy tickets
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2023-03-07 10:59:03 +01:00
Ell eaec725e0e cleaned up privacy page a bit
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2023-03-01 21:50:49 +01:00
Ell 694a0fdd62 deleted the reading post
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2023-03-01 18:46:24 +01:00
Ell 0ccca5e485 fixed blog back to main page links
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2023-03-01 18:44:44 +01:00
Ell 24ed853a7e archived some old posts
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2023-03-01 18:43:46 +01:00
Ell 5e60b8feba improved the way anchors work
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2023-02-12 12:18:45 +01:00
Ell 0e73603716 an update to the actually additions future post
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2023-01-29 13:10:59 +01:00
Ell ed15b68b8a add modrinth links
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2023-01-28 11:18:11 +01:00
Ell cad8382cfd improved MLEM link
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2023-01-25 19:27:48 +01:00
Ell e834a695a2 touchy tickets info update
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2023-01-25 19:16:07 +01:00
Ell f9fc0cc4c8 some small changes
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2023-01-24 22:52:53 +01:00
Ell 5a84f10de0 fixed social buttons on firefox mobile
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2023-01-11 11:20:30 +01:00
Ell 58b483b09b commissions closed info
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2023-01-10 14:25:29 +01:00
Ell 94f2956d8a hard times
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2023-01-06 21:36:03 +01:00
Ell 1fc8b2b265 condense the quotes in the 2022 reading post
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2022-12-19 17:44:21 +01:00
Ell a7430ddbe6 just preparin for the inevitable
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2022-12-18 22:12:58 +01:00
Ell f3be63427f discuss!
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2022-12-13 15:56:50 +01:00
Ell 4cc4e465ec Reading Faves 2022 (and slight blog css changes)
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2022-12-13 15:54:53 +01:00
Ell 5fadb02a5e Merge branch 'master' of https://git.ellpeck.de/Ellpeck/Web
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2022-12-12 09:53:33 +01:00
Ell f5d9816e52 removed google analytics 2022-12-12 09:53:32 +01:00
Ell 10d6b36932 use https for ell.lt links
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2022-12-01 17:25:52 +01:00
Ell 177790b08c moved permanent redirects to ell.lt
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2022-12-01 14:33:34 +01:00
Ell a68297a90d midnights
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2022-11-11 12:58:38 +01:00
Ell ad8113efb5 this really bothered me
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2022-11-02 21:28:32 +01:00
Ell 0cbf7493df organized blog output a bit better
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2022-10-10 15:20:59 +02:00
Ell 5462ba2fa2 add refs to other sites
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2022-10-08 12:51:41 +02:00
Ell 7e8d43301c impressum update
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2022-10-08 12:47:05 +02:00
Ell 83dec6928c gender
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2022-10-08 11:13:24 +02:00
Ell 955f7a1737 updated outdated impressum phone number
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2022-09-25 15:05:09 +02:00
Ell a3d919fb06 add licensing information to terms
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2022-09-23 13:05:40 +02:00
Ell 38ff4424b4 some styling improvements
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2022-09-10 21:53:38 +02:00
Ell e117522b94 made the logo icon thing a bit bigger
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2022-09-10 21:40:58 +02:00
Ell 49c8b87013 Me
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2022-09-10 21:39:21 +02:00
Ell 5af135dd0b improved blog media layout
All checks were successful
Jenkins
Web/pipeline/head This commit looks good
2022-09-10 17:47:57 +02:00
Ell c936d619e5 discuss
All checks were successful
Web/pipeline/head This commit looks good
2022-07-21 21:53:19 +02:00
Ell 2b03678e30 Merge branch 'master' of https://git.ellpeck.de/Ellpeck/Web
All checks were successful
Web/pipeline/head This commit looks good
2022-07-21 21:51:27 +02:00
Ell 94d3e5860d The Cruise That Changed it All 2022-07-21 21:51:26 +02:00
Ell 1a4b5bd162 INFINITE POSTS
All checks were successful
Web/pipeline/head This commit looks good
2022-07-17 15:37:09 +02:00
Ell 2cf7eb39c1 Ell
All checks were successful
Web/pipeline/head This commit looks good
2022-07-17 15:35:53 +02:00
Ell 17d2972ba9 added a mailing list subscription link
All checks were successful
Web/pipeline/head This commit looks good
2022-07-17 15:26:14 +02:00
Ell 67e0e74569 updated dependencies, and improved rss feed summary
All checks were successful
Web/pipeline/head This commit looks good
2022-07-13 13:52:53 +02:00
Ell ade56d6663 update support picture
All checks were successful
Web/pipeline/head This commit looks good
2022-07-12 21:24:29 +02:00
Ell f5647ab1f7 increased commission price
All checks were successful
Web/pipeline/head This commit looks good
2022-07-12 14:11:13 +02:00
Ell 2693b5d483 added a wide version of the logo
All checks were successful
Web/pipeline/head This commit looks good
2022-07-11 17:18:01 +02:00
Ell 72129bd93b added generikb's testimonial
All checks were successful
Web/pipeline/head This commit looks good
2022-06-15 15:20:48 +02:00
Ell 96e96932bd make this look less like "buy me stuff"
All checks were successful
Web/pipeline/head This commit looks good
2022-05-24 00:45:38 +02:00
Ell 7b7a85cc35 Merge branch 'master' of https://git.ellpeck.de/Ellpeck/Web
All checks were successful
Web/pipeline/head This commit looks good
2022-05-21 15:30:59 +02:00
Ell 4c45adebf8 i don't think this is necessary 2022-05-21 15:30:57 +02:00
Ell c083b63eb7 moar sonk
All checks were successful
Web/pipeline/head This commit looks good
2022-05-07 22:13:39 +02:00
Ell 2820218a24 this changed too
All checks were successful
Web/pipeline/head This commit looks good
2022-04-12 12:39:03 +02:00
Ell 8b06a1b545 added another commission and testimonial <3
All checks were successful
Web/pipeline/head This commit looks good
2022-04-10 19:10:40 +02:00
Ell 16cff7b371 added custom frames to projects section
All checks were successful
Web/pipeline/head This commit looks good
2022-03-29 12:01:27 +02:00
Ell ed98320949 Merge branch 'master' of https://git.ellpeck.de/Ellpeck/Web
All checks were successful
Web/pipeline/head This commit looks good
2022-03-22 16:47:00 +01:00
Ell 92525454c1 added a cool new general support image 2022-03-22 16:46:59 +01:00
Ell 454c4abf7f added a "buy my stuff" category to the support section
All checks were successful
Web/pipeline/head This commit looks good
2022-03-20 15:47:13 +01:00
Ell cfb42c116b better button labels
All checks were successful
Web/pipeline/head This commit looks good
2022-03-05 19:31:06 +01:00
Ell 4ab03d840e don't take ME there
All checks were successful
Web/pipeline/head This commit looks good
2022-03-05 19:20:18 +01:00
Ell 608c7ce4d3 added anchor redirects for remaining sections too
All checks were successful
Web/pipeline/head This commit looks good
2022-03-05 14:49:05 +01:00
Ell d69916d5f1 added new support image!
All checks were successful
Web/pipeline/head This commit looks good
2022-03-05 14:40:40 +01:00
Ell aa9f8dbee7 moved main site link to the top of the manual
All checks were successful
Web/pipeline/head This commit looks good
2022-03-05 14:27:13 +01:00
Ell 1289e1ecca updated support link on the actadd manual
All checks were successful
Web/pipeline/head This commit looks good
2022-03-05 14:26:31 +01:00
Ell 490844c5cf added a support section to the website
All checks were successful
Web/pipeline/head This commit looks good
2022-03-05 14:24:45 +01:00
Ell b845186f2d discuss
All checks were successful
Web/pipeline/head This commit looks good
2022-02-16 22:58:02 +01:00
Ell 26ec4aa5d1 Merge branch 'master' of https://git.ellpeck.de/Ellpeck/Web
All checks were successful
Web/pipeline/head This commit looks good
2022-02-16 22:55:53 +01:00
Ell 52e78b6df2 Of Abby, Love and Butt-Shaped Pillows 2022-02-16 22:55:50 +01:00
Ell 021ff5f7b2 added links to the status site
All checks were successful
Web/pipeline/head This commit looks good
2022-02-12 12:22:44 +01:00
Ell 7770a8eea2 Merge branch 'master' of https://git.ellpeck.de/Ellpeck/Web into master
All checks were successful
Web/pipeline/head This commit looks good
2022-02-03 13:42:20 +01:00
Ell 4890f43248 i mooooved 2022-02-03 13:42:19 +01:00
Ell 625a12ffcd manual emoji aw yea
All checks were successful
Web/pipeline/head This commit looks good
2022-01-26 19:05:39 +01:00
Ell 4b1d051b72 shuffle some abouts around
All checks were successful
Web/pipeline/head This commit looks good
2022-01-24 13:00:03 +01:00
Ell f0fbb6883a some improvements to the blog layout on small screen sizes
All checks were successful
Web/pipeline/head This commit looks good
2022-01-10 17:35:26 +01:00
Ell 8a524d8c34 Merge branch 'master' of https://git.ellpeck.de/Ellpeck/Web into master
All checks were successful
Web/pipeline/head This commit looks good
2021-12-28 13:04:28 +01:00
Ell 5ef20f5c9f slight about update 2021-12-28 13:04:23 +01:00
Ell c82c7513cb ch-vrr-ches
All checks were successful
Web/pipeline/head This commit looks good
2021-12-15 20:11:07 +01:00
Ell 1d94295b78 forgot to link to rosaline!
All checks were successful
Web/pipeline/head This commit looks good
2021-12-04 16:42:49 +01:00
Ell 4d9b08378c DISCUSS
All checks were successful
Web/pipeline/head This commit looks good
2021-12-04 16:40:22 +01:00
Ell fd87a3c42b Merge branch 'master' of https://git.ellpeck.de/Ellpeck/Web
All checks were successful
Web/pipeline/head This commit looks good
2021-12-04 16:38:42 +01:00
Ell a197682a2a blog post about alexis hall lmao 2021-12-04 16:38:38 +01:00
Ell 2f7f1d8192 display mature content info per post
All checks were successful
Web/pipeline/head This commit looks good
2021-12-01 18:18:47 +01:00
Ell be430fd461 added an atom feed to the blog
All checks were successful
Web/pipeline/head This commit looks good
2021-12-01 18:12:11 +01:00
Ell c912e4bafa Merge branch 'master' of https://git.ellpeck.de/Ellpeck/Web into master
All checks were successful
Web/pipeline/head This commit looks good
2021-12-01 12:36:30 +01:00
Ell a1e3faa2a6 made press link matching a bit cleaner 2021-12-01 12:36:28 +01:00
Ell 31ff57969e updated these super ancient links
All checks were successful
Web/pipeline/head This commit looks good
2021-11-24 01:52:25 +01:00
Ell 68a446ae0d after all these days
All checks were successful
Web/pipeline/head This commit looks good
2021-11-13 13:57:49 +01:00
Ell 9ec821fb60 note emoji
All checks were successful
Web/pipeline/head This commit looks good
2021-11-13 01:54:23 +01:00
Ell 7d9b70bedc B A B E
All checks were successful
Web/pipeline/head This commit looks good
2021-11-12 15:54:19 +01:00
Ell fc3405fa82 emoji for the donation buttons <3
All checks were successful
Web/pipeline/head This commit looks good
2021-11-11 16:01:10 +01:00
Ell 4aebe9542b default dark mode to the user's color scheme
All checks were successful
Web/pipeline/head This commit looks good
2021-11-11 03:18:40 +01:00
Ell 1be224b23d improved styling of code in blog posts
All checks were successful
Web/pipeline/head This commit looks good
2021-11-11 03:05:33 +01:00
Ell 28e938feb6 added emoji to the website because they look hella good
All checks were successful
Web/pipeline/head This commit looks good
2021-11-10 23:18:12 +01:00
Ell 4236d9ac6c updated the actadd future post slightly
All checks were successful
Web/pipeline/head This commit looks good
2021-11-09 02:59:25 +01:00
Ell c24d2ea943 force to anchor after loading the blog
All checks were successful
Web/pipeline/head This commit looks good
2021-11-08 18:45:31 +01:00
Ell 97422a663d fixed actaddmanual support 2021-11-08 18:38:15 +01:00
Ell d3b0e656f6 removed some old assets 2021-11-08 18:31:52 +01:00
Ell 28aa18b713 redesign donation buttons 2021-11-08 18:28:32 +01:00
Ell cab01a8749 updated impressum and privacy links on commissions site
All checks were successful
Web/pipeline/head This commit looks good
TinyLife/pipeline/head This commit looks good
2021-10-28 17:55:29 +02:00
Ell 023cb367d7 overly specific regexing
All checks were successful
Web/pipeline/head This commit looks good
2021-10-11 14:17:29 +02:00
Ell cfe0ae6e88 redirect legacy anchors in js
All checks were successful
Web/pipeline/head This commit looks good
2021-10-11 14:07:57 +02:00
Ell 4b661cf0a7 remove impossible htaccess rules
All checks were successful
Web/pipeline/head This commit looks good
2021-10-11 13:57:52 +02:00
Ell 0fe42beb59 move imprint and privacy to separate pages
All checks were successful
Web/pipeline/head This commit looks good
2021-10-11 13:43:03 +02:00
Ell e18309a65f fixed the "there are no archived posts" text being displayed under the wrong conditions in the All category
All checks were successful
Web/pipeline/head This commit looks good
2021-09-27 04:12:28 +02:00
Ell a26f393f72 fixed social media titles not using the correct page title
All checks were successful
Web/pipeline/head This commit looks good
2021-09-24 19:06:34 +02:00
Ell 01d9b67020 updated miscellaneous social links
All checks were successful
Web/pipeline/head This commit looks good
2021-09-18 03:16:59 +02:00
Ell e01e665789 shuffled some redirects around
All checks were successful
Web/pipeline/head This commit looks good
2021-09-18 00:57:36 +02:00
Ell 04fbef3d6a remove quarris' link from the commissions site by request
All checks were successful
Web/pipeline/head This commit looks good
2021-09-17 00:41:23 +02:00
Ell 848d825ae5 add a link to the terrible web sims gallery
All checks were successful
Web/pipeline/head This commit looks good
2021-09-10 18:53:18 +02:00
Ell 0604572f69 fixed jekyll for ruby 3
All checks were successful
Web/pipeline/head This commit looks good
2021-09-02 16:16:50 +02:00
Ell b9c7dc9127 bleh
All checks were successful
Web/pipeline/head This commit looks good
2021-08-16 00:42:35 +02:00
Ell 5d561b8592 make scripts expire sooner
All checks were successful
Web/pipeline/head This commit looks good
2021-08-16 00:32:46 +02:00
Ell a44e9c0816 some more quotes yo
All checks were successful
Web/pipeline/head This commit looks good
2021-08-15 21:16:53 +02:00
Ell 2935dabcfc change directory structure slightly
All checks were successful
Web/pipeline/head This commit looks good
2021-08-10 22:11:29 +02:00
Ell ff53a22e69 copy the "commissions closed" info to the bottom
All checks were successful
Web/pipeline/head This commit looks good
2021-08-06 22:26:17 +02:00
Ell 0c8571cfc1 fixed privacy link in cookie notification
All checks were successful
Web/pipeline/head This commit looks good
2021-08-01 00:35:33 +02:00
Ell 82d0fbfc69 added mature content warning to the blog
All checks were successful
Web/pipeline/head This commit looks good
2021-07-27 04:01:50 +02:00
Ell ae598d976e discusssss
All checks were successful
Web/pipeline/head This commit looks good
2021-07-27 03:53:57 +02:00
Ell 38b2c2dd35 added Vince, a Party and Brandon short story
All checks were successful
Web/pipeline/head This commit looks good
2021-07-27 03:52:09 +02:00
Ell d71f909456 improved look of dark mode buttons
All checks were successful
Web/pipeline/head This commit looks good
2021-07-22 07:54:31 +02:00
Ell 62775ebdfd improved htaccess expiry syntax
All checks were successful
Web/pipeline/head This commit looks good
2021-07-15 05:25:49 +02:00
Ell 2e852055d3 fixed blog navigation referencing archived posts
All checks were successful
Web/pipeline/head This commit looks good
2021-07-14 03:54:58 +02:00
Ell 5348dd2be9 fixed the banner not working (in theory)
All checks were successful
Web/pipeline/head This commit looks good
2021-07-14 03:29:18 +02:00
Ell 374d8b65dc fixed blog posts not having a description
All checks were successful
Web/pipeline/head This commit looks good
2021-07-09 03:56:01 +02:00
Ell aa0b67db47 fixed scroll spy on main page
All checks were successful
Web/pipeline/head This commit looks good
2021-07-09 03:52:53 +02:00
Ell 35bf3258d5 separate out the commands
All checks were successful
Web/pipeline/head This commit looks good
2021-07-09 03:23:52 +02:00
Ell c6bd12a412 also copy the manual and commissions
Some checks failed
Web/pipeline/head There was a failure building this commit
2021-07-09 03:20:34 +02:00
Ell fbd5500690 update cache settings for blog-related sites
All checks were successful
Web/pipeline/head This commit looks good
2021-07-09 02:47:57 +02:00
Ell 2a5bd50cc8 added back missing short story
All checks were successful
Web/pipeline/head This commit looks good
2021-07-09 02:33:24 +02:00
Ell c62b03748b downcase social image names
All checks were successful
Web/pipeline/head This commit looks good
2021-07-09 02:28:09 +02:00
Ell ce25a49e61 finished age
Some checks failed
Web/pipeline/head There was a failure building this commit
2021-07-09 02:05:03 +02:00
Ell 6bde3c4ff8 silently convert the entire website to jekyll
Some checks failed
Web/pipeline/head There was a failure building this commit
2021-07-09 01:59:28 +02:00
Ell afcd4037ea updated about section slightly
All checks were successful
Web/pipeline/head This commit looks good
2021-07-07 22:38:29 +02:00
Ell 4433896995 removed foe frenzy
All checks were successful
Web/pipeline/head This commit looks good
2021-07-07 15:35:38 +02:00
Ell b7c9d13021 removed stuff that got moved to a different repo
All checks were successful
Web/pipeline/head This commit looks good
2021-07-07 15:32:03 +02:00
Ell 34db8cca03 organized gitignore and presskit a bit better
All checks were successful
Web/pipeline/head This commit looks good
2021-07-07 04:38:35 +02:00
Ell c70b8af404 update project icon too
All checks were successful
Web/pipeline/head This commit looks good
2021-07-01 16:44:18 +02:00
Ell ce1e9378b1 sneakily update the tiny life assets
All checks were successful
Web/pipeline/head This commit looks good
2021-07-01 16:42:56 +02:00
Ell 45b20fc587 dis guss
All checks were successful
Web/pipeline/head This commit looks good
2021-06-26 01:21:44 +02:00
Ell e1a4056ef5 a blog post about terrible reading habits
All checks were successful
Web/pipeline/head This commit looks good
2021-06-26 01:16:23 +02:00
Ell ac692d402a actually remove the anchors code lmao
All checks were successful
Web/pipeline/head This commit looks good
2021-06-24 00:23:02 +02:00
Ell 052ef46de5 don't add unnecessary anchors if we force to the anchor anyway
All checks were successful
Web/pipeline/head This commit looks good
2021-06-24 00:21:16 +02:00
Ell 851c534879 removed invalid analytics link
Some checks reported errors
Web/pipeline/head This commit looks good
Ellpeck/Web/pipeline/head Something is wrong with the build of this commit
2021-06-06 21:54:02 +02:00
Ell 2633e328dd added press kit link to tiny life site
All checks were successful
Web/pipeline/head This commit looks good
2021-06-06 21:07:23 +02:00
Ell 5c985ee1ee fixed up some press issues
All checks were successful
Web/pipeline/head This commit looks good
2021-06-06 20:51:01 +02:00
Ell d5c0de52f0 added presskit
Some checks failed
Web/pipeline/head There was a failure building this commit
2021-06-06 20:40:19 +02:00
Ell 4e1ef46af3 deny web access to some stuff
All checks were successful
Web/pipeline/head This commit looks good
2021-06-06 18:58:11 +02:00
Ell 0cfa93f3dc added the steam page link
All checks were successful
Web/pipeline/head This commit looks good
2021-06-01 20:10:35 +02:00
Ell efc7bdbbec fixed some mobile display inconsistencies
All checks were successful
Web/pipeline/head This commit looks good
2021-05-31 12:16:10 +02:00
Ell 3ef5a76a64 updated tiny life link in projects
All checks were successful
Web/pipeline/head This commit looks good
2021-05-31 00:04:51 +02:00
Ell 93a50aedb5 updated google id
All checks were successful
Web/pipeline/head This commit looks good
2021-05-28 22:13:57 +02:00
Ell e2f202efbd added tiny life site
All checks were successful
Web/pipeline/head This commit looks good
2021-05-28 22:03:29 +02:00
Ell 82b46f9317 discuss
All checks were successful
Web/pipeline/head This commit looks good
2021-05-28 00:54:32 +02:00
Ell 2ccf90d566 new short story blog post, yay
All checks were successful
Web/pipeline/head This commit looks good
2021-05-28 00:52:27 +02:00
Ell c7be739a53 discuss
All checks were successful
Web/pipeline/head This commit looks good
2021-05-24 19:42:35 +02:00
Ell 24c375fd2d cursed comment time
All checks were successful
Web/pipeline/head This commit looks good
2021-05-24 19:37:12 +02:00
Ell c263947095 link to Quarris commissions
All checks were successful
Web/pipeline/head This commit looks good
2021-05-18 14:08:32 +02:00
Ell 0ee1a8e552 summmary
All checks were successful
Web/pipeline/head This commit looks good
2021-05-15 17:13:07 +02:00
Ell 6c804d8e09 added an info about commissions being closed
All checks were successful
Web/pipeline/head This commit looks good
2021-05-15 17:08:56 +02:00
Ell 8207ab2f1d discusserino
All checks were successful
Web/pipeline/head This commit looks good
2021-04-23 22:08:19 +02:00
Ell 2285c97bf1 jed's things to avoid in life
All checks were successful
Web/pipeline/head This commit looks good
2021-04-23 22:05:32 +02:00
Ell 857efd717e OUTskirts
All checks were successful
Web/pipeline/head This commit looks good
2021-04-16 01:35:27 +02:00
Ell 898ee1069d moved linkedin to about
All checks were successful
Web/pipeline/head This commit looks good
2021-04-12 02:44:22 +02:00
Ell 0ea400629f added gitea link
All checks were successful
Web/pipeline/head This commit looks good
2021-04-12 02:35:59 +02:00
Ell 03aeb88031 added women
All checks were successful
Web/pipeline/head This commit looks good
2021-04-10 20:13:37 +02:00
Ell f8acf2aa59 updated commission terms wording
All checks were successful
Web/pipeline/head This commit looks good
2021-04-07 04:44:56 +02:00
Ell 9df7ecad8e fixed book headings being reduced in blog posts
All checks were successful
Web/pipeline/head This commit looks good
2021-04-06 19:29:04 +02:00
Ell e8b22f684b added nature's starlight
All checks were successful
Web/pipeline/head This commit looks good
2021-04-05 01:21:40 +02:00
Ell 31f9b9ae61 some small improvements and alt text fixes
All checks were successful
Web/pipeline/head This commit looks good
2021-04-04 12:45:33 +02:00
Ell a1431fdd9a small fixes
All checks were successful
Web/pipeline/head This commit looks good
2021-04-04 02:01:25 +02:00
Ell 23785fbb8e made blog category links a lot nicer
All checks were successful
Web/pipeline/head This commit looks good
2021-04-04 01:54:29 +02:00
Ell 9561ef22e9 D I S C U S S
All checks were successful
Web/pipeline/head This commit looks good
2021-04-02 18:03:49 +02:00
Ell c0ea135437 Emily's fake boyfriend
All checks were successful
Web/pipeline/head This commit looks good
2021-04-02 18:00:05 +02:00
Ell 1eb200f2ad ensure the manual uses the 1.12 branch
All checks were successful
Web/pipeline/head This commit looks good
2021-04-01 16:38:45 +02:00
Ell b9d7c75469 let's stick with bootstrap 4
All checks were successful
Web/pipeline/head This commit looks good
2021-04-01 04:07:54 +02:00
Ell e8f8a2a002 prefer www-less versions of ellpeck.de
All checks were successful
Web/pipeline/head This commit looks good
2021-04-01 03:56:04 +02:00
Ell 80aabbf88f dependency updates and accessibility improvements
All checks were successful
Web/pipeline/head This commit looks good
2021-04-01 03:47:27 +02:00
Ell 37138b67f8 fixed some issues with the new book styling
All checks were successful
Web/pipeline/head This commit looks good
2021-04-01 00:45:26 +02:00
Ell a33d718416 extract some stuff to utility class
All checks were successful
Web/pipeline/head This commit looks good
2021-03-31 19:56:33 +02:00
Ell 798e8cd8e3 fixed rss feed generator
All checks were successful
Web/pipeline/head This commit looks good
2021-03-31 19:36:32 +02:00
Ell 9270cf8bed added short story look and feel and published Em & Ben
Some checks failed
Web/pipeline/head There was a failure building this commit
2021-03-31 19:24:36 +02:00
Ell d171f55eea fixed the "all posts" category not being recognized
All checks were successful
Web/pipeline/head This commit looks good
2021-03-31 17:03:25 +02:00
Ell 7470a20886 made the blog category a search string
All checks were successful
Web/pipeline/head This commit looks good
2021-03-31 17:00:15 +02:00
Ell 8e2dc6f2f4 change the distance between blog category buttons a bit
All checks were successful
Web/pipeline/head This commit looks good
2021-03-31 16:12:34 +02:00
Ell ea411c3599 some minor rss feed fixes
All checks were successful
Web/pipeline/head This commit looks good
2021-03-31 03:03:58 +02:00
Ell 513f54bb2e only link to posts with the same main category
All checks were successful
Web/pipeline/head This commit looks good
2021-03-31 02:55:17 +02:00
Ell 0271b1563c blog category description
All checks were successful
Web/pipeline/head This commit looks good
2021-03-31 02:52:47 +02:00
Ell 5de5fba62a added a featured category to the blog
All checks were successful
Web/pipeline/head This commit looks good
2021-03-31 02:48:13 +02:00
Ell bebf4fb309 slight commission terms changes
All checks were successful
Web/pipeline/head This commit looks good
2021-03-29 21:59:38 +02:00
Ell eae6b184e4 ensure cache-control is edited correctly
All checks were successful
Web/pipeline/head This commit looks good
2021-03-26 17:40:46 +01:00
Ell a037bf2483 it's one of those days, I guess
All checks were successful
Web/pipeline/head This commit looks good
2021-03-26 17:32:28 +01:00
Ell 93a8dfe6cd fix max age being set incorrectly
All checks were successful
Web/pipeline/head This commit looks good
2021-03-26 17:30:15 +01:00
Ell 5755a2f883 please work
Some checks failed
Web/pipeline/head There was a failure building this commit
2021-03-26 17:18:17 +01:00
Ell ec318ad8d8 use merge in htaccess
All checks were successful
Web/pipeline/head This commit looks good
2021-03-26 17:17:23 +01:00
Ell 5941197bc0 set the correct cache control max age
All checks were successful
Web/pipeline/head This commit looks good
2021-03-26 17:15:32 +01:00
Ell cda085cb8b whoop #2
All checks were successful
Web/pipeline/head This commit looks good
2021-03-26 17:14:36 +01:00
Ell dcbe0afaa1 whoop
All checks were successful
Web/pipeline/head This commit looks good
2021-03-26 17:13:10 +01:00
Ell 4c2a12849c improve blog header setting
All checks were successful
Web/pipeline/head This commit looks good
2021-03-26 17:12:00 +01:00
Ell cabefb07f7 fixed cache-control header being set twice in blog
All checks were successful
Web/pipeline/head This commit looks good
2021-03-26 17:06:03 +01:00
Ell 341da9640b added reedsy prompts to website
All checks were successful
Web/pipeline/head This commit looks good
2021-03-26 04:53:17 +01:00
Ell aaca91000b some minor style improvements
All checks were successful
Web/pipeline/head This commit looks good
2021-03-24 19:54:14 +01:00
Ell e23f33b85b dang it, removed debug code
All checks were successful
Web/pipeline/head This commit looks good
2021-03-24 19:36:31 +01:00
Ell 6724f352c6 updated the banner to look a bit nicer
All checks were successful
Web/pipeline/head This commit looks good
2021-03-24 19:36:12 +01:00
Ell 71c5f58d30 made the current category button disabled
All checks were successful
Web/pipeline/head This commit looks good
2021-03-24 17:38:02 +01:00
Ell cbe4a615df added some cool seasonal banners
All checks were successful
Web/pipeline/head This commit looks good
2021-03-21 20:20:15 +01:00
Ell 49558c0f74 added discussion link
All checks were successful
Web/pipeline/head This commit looks good
2021-03-21 16:40:34 +01:00
Ell 24d8285d04 added modding ama post
All checks were successful
Web/pipeline/head This commit looks good
2021-03-21 16:39:28 +01:00
Ell c12bfeb900 fixed patreon image having weird width and height
All checks were successful
Web/pipeline/head This commit looks good
2021-03-20 19:18:50 +01:00
Ell bbd38df001 added another commission and made the commissions site less tedious to maintain
All checks were successful
Web/pipeline/head This commit looks good
2021-03-20 19:04:13 +01:00
Ell d3ebe92ed7 display an info for categories with no archive
All checks were successful
Web/pipeline/head This commit looks good
2021-03-20 16:31:03 +01:00
Ell 8a14c03989 some code style improvements
All checks were successful
Web/pipeline/head This commit looks good
2021-03-19 12:02:31 +01:00
Ell 40246ef683 some more minor improvements
All checks were successful
Web/pipeline/head This commit looks good
2021-03-19 11:42:35 +01:00
Ell b239ec5b02 get rid of the useless patreon widget
All checks were successful
Web/pipeline/head This commit looks good
2021-03-19 08:33:18 +01:00
Ell ff133309db set cookies globally for the site
All checks were successful
Web/pipeline/head This commit looks good
2021-03-19 07:59:36 +01:00
Ell ee5c7b64f0 overhauled most of the script code
All checks were successful
Web/pipeline/head This commit looks good
2021-03-19 07:55:18 +01:00
Ell f4a041bfec some load performance improvements
All checks were successful
Web/pipeline/head This commit looks good
2021-03-19 04:23:04 +01:00
Ell c1d01df228 fetch changes before checking out
All checks were successful
Web/pipeline/head This commit looks good
2021-03-17 21:14:53 +01:00
Ell ccdf68e60a don't use env
Some checks failed
Web/pipeline/head There was a failure building this commit
2021-03-17 21:07:25 +01:00
Ell 21b8fbb09f always check out the exact commit
Some checks failed
Web/pipeline/head There was a failure building this commit
2021-03-17 21:05:55 +01:00
Ell 15d6f5f674 fine I give up
Some checks failed
Web/pipeline/head There was a failure building this commit
2021-03-17 19:46:58 +01:00
Ell c87e5ad8a8 why is it not telling me what's wrong anywhere
Some checks are pending
Web/pipeline/head Build queued...
2021-03-17 19:31:55 +01:00
Ell ec308fc050 use full path?
Some checks failed
Web/pipeline/head There was a failure building this commit
2021-03-17 19:29:05 +01:00
Ell 08ce564bfa ?
Some checks failed
Web/pipeline/head There was a failure building this commit
2021-03-17 19:27:17 +01:00
Ell a5067d2406 add a label
Some checks failed
Web/pipeline/head There was a failure building this commit
2021-03-17 19:23:34 +01:00
Ell 95d15e026a try using customWorkspace for the jenkins build
Some checks failed
Web/pipeline/head There was a failure building this commit
2021-03-17 19:21:46 +01:00
Ell 8a5aec6867 remove html rewrite in favor of MultiViews
Some checks failed
Web/pipeline/head There was a failure building this commit
2021-03-17 16:23:37 +01:00
Ell 33db928a7c some rss feed improvements
All checks were successful
Web/pipeline/head This commit looks good
2021-03-17 03:30:30 +01:00
Ell 54282c31bb get rid of global variables because js is bad
All checks were successful
Web/pipeline/head This commit looks good
2021-03-17 03:10:25 +01:00
Ell 8cd72547f0 added categories to the blog
All checks were successful
Web/pipeline/head This commit looks good
2021-03-17 03:07:12 +01:00
Ell 3f5e2ac4de organize the .htaccess file a bit
All checks were successful
Web/pipeline/head This commit looks good
2021-03-17 00:23:49 +01:00
Ell 5f565cffbb added redirect for old blog posts
All checks were successful
Web/pipeline/head This commit looks good
2021-03-17 00:09:44 +01:00
Ell 6be0f6cbaf moved blog sources into blog/src
All checks were successful
Web/pipeline/head This commit looks good
2021-03-11 03:44:50 +01:00
Ell fe510da05a move blog resources to blog directory
All checks were successful
Web/pipeline/head This commit looks good
2021-03-11 03:43:11 +01:00
Ell 9c3b8f16ae fixed some outdated links
All checks were successful
Web/pipeline/head This commit looks good
2021-03-11 03:32:22 +01:00
Ell 23eea1d559 moved the blog to its own directory
All checks were successful
Web/pipeline/head This commit looks good
2021-03-11 03:21:23 +01:00
Ell 2be60f2c6d added app-ads
All checks were successful
Web/pipeline/head This commit looks good
2021-03-02 03:41:53 +01:00
Ell 9afeda372a update to new google analytics id
All checks were successful
Web/pipeline/head This commit looks good
2021-03-02 02:26:02 +01:00
Ell c77be9c43a an old obsession
All checks were successful
Web/pipeline/head This commit looks good
2021-02-16 20:22:45 +01:00
Ell b4e472713c so inviting, I almost jump in
All checks were successful
Web/pipeline/head This commit looks good
2021-01-27 05:14:35 +01:00
Ell e512260184 commission info about MC versions
All checks were successful
Web/pipeline/head This commit looks good
2021-01-15 19:40:17 +01:00
Ell b443c9ccf7 added more flower bushes testimonial
All checks were successful
Web/pipeline/head This commit looks good
2021-01-04 22:15:14 +01:00
Ell 15aab19d37 english
All checks were successful
Web/pipeline/head This commit looks good
2021-01-03 14:44:49 +01:00
Ell ae4b1c8442 fixed commission link in about
All checks were successful
Web/pipeline/head This commit looks good
2021-01-03 14:36:30 +01:00
235 changed files with 5541 additions and 4940 deletions

View file

@ -0,0 +1,27 @@
on:
push:
branches: [master]
jobs:
web:
runs-on: ubuntu-latest
env:
BUNDLE_GEMFILE: ${{ github.workspace }}/main/Gemfile
steps:
- name: Clone repository
uses: actions/checkout@v4
- name: Setup Ruby
uses: https://github.com/ruby/setup-ruby@v1
with:
ruby-version: 3.2.2
bundler-cache: true
- name: Build
run: cd main && bundle exec jekyll build
- name: Deploy
run: |
apt update && apt install -y rsync
curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb && sudo dpkg -i cloudflared.deb
echo "${{ secrets.ELLBOT_KEY }}" > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa
rsync -rv --delete -e 'ssh -o "ProxyCommand cloudflared access ssh --hostname %h" -o "StrictHostKeyChecking=no"' main/_site/ ellbot@ssh.ellpeck.de:/var/www/ellpeck

9
.gitignore vendored
View file

@ -1,6 +1,3 @@
node_modules
sitemap.xml
feed.json
rss.xml
atom.xml
blog-*.html
.jekyll-*
_site
.idea

View file

@ -1,29 +0,0 @@
Options -Indexes
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule (.*) $1.html [L]
RewriteRule ^discord/?$ "https://discord.gg/Uy2ZM9X" [R=301,L]
RewriteRule ^actaddchangelog/?$ "https://github.com/Ellpeck/ActuallyAdditions/blob/main/update/changelog.md" [R=301,L]
RewriteRule ^actadddownload/?$ "https://minecraft.curseforge.com/projects/actually-additions/files" [R=301,L]
RewriteRule ^actaddlicense/?$ "https://github.com/Ellpeck/ActuallyAdditions/blob/main/LICENSE.md" [R=301,L]
RewriteRule ^actadd/?$ "https://minecraft.curseforge.com/projects/actually-additions" [R=301,L]
RewriteRule ^projects/?$ "https://ellpeck.de/#projects" [NE,R=301,L]
RewriteRule ^impressum/?$ "https://ellpeck.de/#impressum" [NE,R=301,L]
RewriteRule ^privacy/?$ "https://ellpeck.de/#privacy" [NE,R=301,L]
RewriteRule ^mc/?$ "https://ellpeck.de/minecraft-stuff" [R=301,L]
RewriteRule ^minecraft-stuff/?$ "https://ellpeck.de/projects" [R=301,L]
RewriteRule ^yt/?$ "https://www.youtube.com/c/ellpeck" [R=301,L]
RewriteRule ^wishlist/?$ "https://www.amazon.de/hz/wishlist/ls/LZO9Y2Z3VJ5Q?&sort=default" [R=301,L]
RewriteRule ^fftranslate/?$ "https://poeditor.com/join/project/ElzC23ecB6" [R=301,L]
ErrorDocument 404 /404.html
<FilesMatch "^(blog|sitemap|feed|atom|rss)">
ExpiresActive On
ExpiresDefault A1
Header append Cache-Control must-revalidate
</FilesMatch>

24
Jenkinsfile vendored
View file

@ -1,24 +0,0 @@
pipeline {
agent any
stages {
stage('Pull') {
when {
branch 'master'
}
steps {
sh '''cd /var/www/ellpeck
git pull'''
}
}
stage('Node') {
when {
branch 'master'
}
steps {
sh '''cd /var/www/ellpeck/node
node blog.js
node rss.js'''
}
}
}
}

View file

@ -1,58 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto">
<link rel="stylesheet" href="style.css">
<title>Actually Additions Manual</title>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
<script src="../scripts/util.js"></script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-150032076-2"></script>
<script>
window.dataLayer = window.dataLayer || [];
let gtag = function () {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'UA-150032076-2');
</script>
<script data-ad-client="ca-pub-5754829579653773" async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
</head>
<body>
<div id="sidebar">
<!-- Sponsor buttons -->
<div class="sellout">
<iframe src="https://github.com/sponsors/Ellpeck/button" title="Sponsor Ellpeck" height="32" width="116" style="border: 0;"></iframe>
<a href="https://www.patreon.com/bePatron?u=2494595" data-patreon-widget-type="become-patron-button"></a>
<script async src="https://c6.patreon.com/becomePatronButton.bundle.js"></script>
</div>
<hr>
</div>
<div id="content">
<div id="entries">
<!-- Cookie notification -->
<script src="../scripts/cookieinfo.js"></script>
</div>
<script src="index.js"></script>
</div>
<div id="footer">
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
<ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-5754829579653773" data-ad-slot="1438750467" data-ad-format="horizontal" data-full-width-responsive="true"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
</div>
</body>
</html>

View file

@ -1,129 +0,0 @@
[{
"name": "Blogs are Cool, I Think",
"summary": "The first post and how I created it",
"id": "blogs_are_cool",
"date": "2/17/2019",
"discuss": "https://twitter.com/Ellpeck/status/1096937184601538566",
"archived": true
},
{
"name": "Why You Should Mod Minecraft",
"summary": "About what makes Minecraft modding great and why you should probably try it if you enjoy programming",
"id": "why_you_should_mod_minecraft",
"date": "2/17/2019",
"discuss": "https://twitter.com/Ellpeck/status/1097177774337462272"
},
{
"name": "Kindling the Reading Flame",
"summary": "About the Kindle, Harry Potter and what I like and dislike about them",
"id": "reading",
"date": "3/22/2019",
"discuss": "https://twitter.com/Ellpeck/status/1109102077911973888"
},
{
"name": "Small Projects",
"summary": "Why creating a small, unplanned project is sometimes good for you",
"id": "small_projects",
"date": "5/1/2019",
"discuss": "https://twitter.com/Ellpeck/status/1123651624201871360"
},
{
"name": "About Cross-Platform and Motivation",
"summary": "How moving from Java to C# taught me how horrible it is to create a cross-platform application with little to no knowledge or documentation",
"id": "cross_platform_trainwreck",
"date": "7/6/2019",
"discuss": "https://twitter.com/Ellpeck/status/1147502654236573697",
"archived": true
},
{
"name": "Big Projects",
"summary": "How a once small project I even created a post about turned into the first game I'm selling: Foe Frenzy",
"id": "big_projects",
"date": "9/15/2019",
"discuss": "https://twitter.com/Ellpeck/status/1173247686654517249"
},
{
"name": "How to make a Rock Bottom mod",
"summary": "My adventures back into a game I stopped working on about two years ago and how I start on a mod for it",
"id": "rock_bottom_mod",
"date": "10/3/2019",
"discuss": "https://twitter.com/Ellpeck/status/1180092634410487808"
},
{
"name": "Java Tutorial, Part 1: Hello World",
"summary": "The first part of my post series for programming beginners where I explain how to write code in Java.",
"id": "java_1",
"date": "10/10/2019",
"discuss": "https://twitter.com/Ellpeck/status/1182080078827737088"
},
{
"name": "Java Tutorial, Part 2: Intro to Conditions and Loops",
"summary": "The second part of my post series for programming beginners. This one is all about conditions and loops.",
"id": "java_2",
"date": "10/10/2019",
"discuss": "https://twitter.com/Ellpeck/status/1182354544707198976"
},
{
"name": "Java Tutorial, Part 3: (Static) Methods",
"summary": "In this Java tutorial for beginners, we cover what (static) methods, parameters and return types are.",
"id": "java_3",
"date": "10/11/2019",
"discuss": "https://twitter.com/Ellpeck/status/1182775985885847558"
},
{
"name": "Java Tutorial, Part 4: Classes and Objects",
"summary": "In this Java tutorial for beginners, we cover the basics of creating classes with a constructor, some fields and some methods and creating objects of them.",
"id": "java_4",
"date": "10/14/2019",
"discuss": "https://twitter.com/Ellpeck/status/1183857460660101133"
},
{
"name": "Java Tutorial, Part 5: Things I Left Out So Far",
"summary": "In this Java tutorial for beginners, we cover some shorthands, some more data types, the difference between pass-by-reference and pass-by-value, null, as well as arrays and lists.",
"id": "java_5",
"date": "10/17/2019",
"discuss": "https://twitter.com/Ellpeck/status/1184894859133509632"
},
{
"name": "Lows",
"summary": "About depression and what it feels like when I don't know what to do with myself",
"id": "lows",
"date": "10/20/2019",
"discuss": "https://twitter.com/Ellpeck/status/1186028260838334471",
"archived": true
},
{
"name": "Java Tutorial, Part 6: Inheritance",
"summary": "In this Java tutorial for beginners, we cover classes extending other classes and the instanceof keyword.",
"id": "java_6",
"date": "10/31/2019",
"discuss": "https://twitter.com/Ellpeck/status/1189904487722487809"
},
{
"name": "Java Tutorial, Part 7: Overriding Methods",
"summary": "In this Java tutorial for beginners, we cover overriding methods, calling superclass methods and toString().",
"id": "java_7",
"date": "11/26/2019",
"discuss": "https://twitter.com/Ellpeck/status/1199339701640945664"
},
{
"name": "But Do You Really Care?",
"summary": "On taking a break from social media",
"id": "but_do_you_really_care",
"date": "5/6/2020"
},
{
"name": "Oh God, Please Don't Port Actually Additions",
"summary": "As Actually Additions celebrates its fifth birthday, I break down what I like and dislike about it.",
"id": "actually_additions",
"date": "5/10/2020",
"discuss": "https://twitter.com/Ellpeck/status/1259600490377216002"
},
{
"name": "The Future of Actually Additions",
"summary": "Not wanting to accept the fate of Actually Additions, someone has come to its rescue. 1.16, here we come?",
"id": "future_actually_additions",
"date": "11/23/2020",
"discuss": "https://twitter.com/Ellpeck/status/1330938597785169925"
}
]

View file

@ -1,19 +0,0 @@
Recently, my boyfriend gave me his old Amazon Kindle (because he got one of the newer models). This inspired me, after about four or five years of not really picking up a book outside of school, to start reading for fun again. The first book that I "picked up" was Harry Potter and the.. stone.. of the wise ("Harry Potter and the Philosopher's Stone" apparently).
# The book
I've never written a book review before, but since Harry Potter is kind of a really popular genre and this is the first time I read one of the books, I wanted to give my opinion on it.
Now usually, I don't really enjoy the fantasy genre. Something about it is just a bit uninteresting to me, and I don't necessarily enjoy reading something that's unrelated to (my) reality. I usually need entertainment to be somewhat relatable to me and my life, and fantasy and science fiction don't always do that for me; I don't really enjoy things like Star Wars, but I do enjoy, for example, the Netflix show Black Mirror, because it touches topics that I do come in contact with in my own life.
But because the first Harry Potter book introduced the entire thing in such a great way, making the reader follow along with Harry's journey of _also_ figuring out all of the magic stuff that is new to both him and the reader, it felt like a lot better of an introduction to me.
Reading the first three quarters or so of the book, I honestly had a blast. The story was being told so well and all of the details and emotions were depicted so extensively, the places were described with a depth that made me be able to envision them perfectly in my head.
But then, to my (negative) surprise, suddenly, towards the last couple of chapters of the book, the tone shifted. All of a sudden, the story was being told in such a rushed, quick way; especially what should've been the climax of the book. To me, it felt like the author was getting scared of missing her deadline and stopped writing in the way that I had started to love so much.
I would definitely say that I enjoyed the book (which is saying quite a lot, because fantasy is definitely not my kind of genre), and I plan on also reading the other entries in the series, but to me, it felt like the later in the book I was, the more the quality of what I was reading was declining, which was such a bummer to me because J.K. Rowling seems to be a _really_ good author.
_Also, regarding the stuff that's happening with whatever J.K. is saying right now: If she or anyone else thinks it's okay to retroactively pretend like there's diversity where there simply isn't any: In my opinion, it's not okay. Just accept that your writing in that regard just isn't up to the standard of what it should be in the 21st century. Characters shouldn't be gay, black or another minority for the sake of "showing diversity" or "making a statement", but because diversity like that just happens to exist in the real world._
# The Kindle
There's honestly not much to say about the Kindle, because everything that I could say can be easily summed up into this: _I absolutely love it_. It's one of the older generation Paperwhites (maybe even the first one, I'm not sure), and the e-ink is amazing, the menus are easy to navigate, being able to change the font is great. It's just all round an awesome experience, especially because, with Amazon Prime, which pretty much everyone seems to have, you have Prime Reading, which allows you to read quite a big amount of books for free (without even having to get Kindle Unlimited).
Seriously, if you enjoy reading, but don't enjoy carrying heavy books around with you while you're traveling, I really think you should get a Kindle.

View file

@ -1,128 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ellpeck's Mod Commissions</title>
<meta name="author" content="Ellpeck">
<meta name="description" content="I accept commissions for Minecraft mods. If you want a mod of any size and complexity made, this is the place to go!">
<meta name="keywords" content="Ellpeck, Minecraft, Mods, Modding, Commission, Forge, Plugin">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto">
<link rel="stylesheet" href="style.css">
<link rel="icon" href="../favicon.ico">
<meta property="og:title" content="Ellpeck's Mod Commissions">
<meta property="og:description" content="I accept commissions for Minecraft mods. If you want a mod of any size and complexity made, this is the place to go!">
<meta property="og:image" content="https://www.ellpeck.de/res/logoSmall.png">
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@Ellpeck">
<meta name="twitter:creator" content="@Ellpeck">
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-150032076-2"></script>
<script>
window.dataLayer = window.dataLayer || [];
let gtag = function () {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'UA-150032076-2');
</script>
</head>
<body>
<div class="par">
<h1>About Me</h1>
<div class="row">
<div class="col-md-auto">
<img class="rounded" width="180" height="180" src="../res/me.png">
</div>
<div class="col">
<p>Hi, I'm Ellpeck, a student and programmer from Germany. I've been making Minecraft mods for over 6 years, and during that time, I created some popular mods like <a href="https://www.curseforge.com/minecraft/mc-mods/actually-additions">Actually Additions</a> and <a href="https://www.curseforge.com/minecraft/mc-mods/natures-aura">Nature's Aura</a>. If you want to know more about my projects, you should check out <a href="https://ellpeck.de">my website</a>.</p>
<p>Recently, I started accepting commissions for mods as well. If you want a Minecraft mod of any size and complexity made, I can do that for you. ❤️</p>
</div>
</div>
</div>
<div class="par">
<h1>Past Commissions</h1>
<div class="mod-info">
<img src="media/slingshot.png" style="object-position: bottom">
<div class="mod-caption">
<h2><a href="https://www.curseforge.com/minecraft/mc-mods/slingshot">Slingshot</a></h2>
<p class="d-none d-sm-block">Slingshot allows you to shoot different items with your slingshot for different effects</p>
</div>
</div>
<div class="test test-left">
<p>A pleasant, kind, and easy to work with modder. He produces high quality work and is, in my opinion, well worth the money.</p>
<p class="test-name">Violet</p>
</div>
<div class="mod-info">
<img src="media/nyx.png" style="object-position: top">
<div class="mod-caption">
<h2><a href="https://www.curseforge.com/minecraft/mc-mods/nyx">Nyx</a></h2>
<p class="d-none d-sm-block">Nyx is a mod that transforms and improves Minecraft's time of darkness by adding elements and events themed around the moon, stars, and night sky</p>
</div>
</div>
<div class="test test-right">
Ellpeck has proven time and again to be very talented and open-minded when it comes to mod commissions. Easy and enjoyable to work with, and very patient when it comes to working out any inconsistencies. Im happy to have collaborated with him!
<p class="test-name">Drakallen</p>
</div>
<div class="mod-info">
<img src="media/enchantmentstorage.png">
<div class="mod-caption">
<h2><a href="https://www.curseforge.com/minecraft/mc-mods/enchantment-storage">Enchantment Storage</a></h2>
<p class="d-none d-sm-block">Stores and automatically converts your enchanted books into a limitless repository of knowledge</p>
</div>
</div>
<div class="test test-left">
Working with Ellpeck was an immense pleasure, he was attentive and convivial during the entire process. Upon release, I was assured any and all problems would be handled immediately, although I have yet to encounter a single issue. Brilliant work and highly recommended.
<p class="test-name">Zilch</p>
</div>
</div>
<div class="par">
<a id="terms"></a>
<h1>Terms and Pricing</h1>
<p>Here is all the information you need if you want to commission me to make a mod for you:</p>
<ul>
<li>I create Minecraft Forge mods only.</li>
<li>I work in Minecraft Versions 1.12.2 and upwards only.</li>
<li>No ports of existing mods or content.</li>
<li>No content that can't be created using "traditional" Java code (coremods).</li>
<li>You will have the choice of whether you want to receive the source code privately or whether I should publish it on GitHub.</li>
<li>You will have the choice of whether you want to be sent the mod jar or whether I should publish the mod on CurseForge. If the mod is published on CurseForge, you will get up to 50% of the share of Curse Points generated through the mod.</li>
<li>After the mod is completed, bug fixes are included for free, but feature updates and Minecraft version updates are not. They can be requested at a later date for a small fee though.</li>
<li>I charge between 10€ ($11) and 30€ ($33) an hour.
<ul>
<li>I estimate the total amount of time it will take based on the size and complexity of the mod beforehand.</li>
<li>I request half of the estimated amount to be paid before I start working on the mod, the other half once I'm finished. That way, there's security for both of us.</li>
<li>If a feature takes much more time than expected, I might raise the second payment's price slightly. Of course, if I do this, I will also provide an explanation as to why this is necessary.</li>
</ul>
</li>
<li>You have to be at least 18 years old, and has to pay with their own PayPal account. I don't accept other forms of payment.</li>
</ul>
</div>
<div class="par">
<h1>Contact</h1>
<p>If you're interested, you can easily contact me by joining my Discord server and messaging me privately from there. Alternatively, you can also <a href="mailto:me@ellpeck.de">send me an email</a>.</p>
<a href="https://ellpeck.de/discord"><img class="discord-img" src="../res/discord.png"></a>
</div>
<div class="footer">
<a href="https://git.ellpeck.de/Ellpeck/Web">&copy; 2021 Ellpeck</a> &ndash; <a href="https://ellpeck.de/#impressum">Impressum</a> &ndash; <a href="https://ellpeck.de/#privacy">Privacy</a>
</div>
</body>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 B

View file

@ -1,214 +0,0 @@
# Creating Custom Maps
Foe Frenzy has a simple system in place that allows you to create your own maps using a simple image editing software as well as a text editor. To play a map, all you have to do is put it into a special folder and the game will automatically load it up. If you want to play a custom map in Multiplayer, you need to make sure that all players have the map installed.
## Where to Put Map Files
All files that the game uses are stored in `Documents/Foe Frenzy` on Windows or `~/Foe Frenzy` on Linux. After launching the game for the first time, a `Maps` folder and a `SpawnPools` folder will be generated. This is the location where custom maps live.
## Creating a Map
When creating a Map, two files are required:
- `Maps/MapName.xml`: This is a file containing information about the map, like which tile is which, where items can spawn, where players can spawn and so on.
Note that the example file included has some more comments inside of it that explain what the different parts of the file do.
- `Maps/Textures/MapName.png`: This file represents the actual layout of the map. You can edit it in an image editor of your choice. Each pixel represents one tile on the map, and the colors of the pixels represent what tiles should be placed there. The size of the image is the same as the size of the finished map.
Note that, further down on this page, there is some information about which colors the game's default tiles have in the map image.
Below is the file as well as the image for an example map with some comments that explain the structure in greater detail. To get started making your own map, it would be easiest to copy this information.
```xml
<?xml version="1.0"?>
<!-- XML header information, don't change this -->
<RawMap xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<!-- A short description of the map that displays when it's selected to be played -->
<Description>A test custom map</Description>
<!-- The amount of seconds it takes until an item spawner spawns an item -->
<ItemSpawnerDelay>5</ItemSpawnerDelay>
<!-- The weather effect that is displayed on the map. Possible values are Snow and Rain -->
<WeatherEffect>Snow</WeatherEffect>
<!-- The type of music that should be displayed on the map. Scroll down for reference on all types -->
<MusicTheme>Lava</MusicTheme>
<!-- The map's global light modifier. This is the amount of "sunlight" that the map has, where 255 for each R, G, B value means "full light" and 0 means "no light". -->
<LightModifier>
<B>255</B>
<G>255</G>
<R>255</R>
<A>255</A>
</LightModifier>
<!-- The intensity of lights (like torches) on the map, 1 means full intensity, 0 means that they give off no light at all -->
<LightIntensity>0</LightIntensity>
<!-- The tile that is placed outside the borders of the map for visual purposes if the map is smaller than the screen size -->
<FillerTile>Grass</FillerTile>
<!-- The structure that is placed on top of the filler tile -->
<FillerStructure>Tree</FillerStructure>
<!-- Tile properties are custom combinations of tiles and structures that can be placed in specific locations using the map image. If multiple tile properties are required, an entire RawTileProperty can be copied and pasted below any number of times. -->
<TileProperties>
<RawTileProperty>
<!-- The color specified in the map image file to make this specific property occur in the map -->
<Color>
<B>151</B>
<G>255</G>
<R>151</R>
<A>255</A>
</Color>
<Tile>Grass</Tile>
<Structure>Vine</Structure>
</RawTileProperty>
</TileProperties>
<!-- This is the list of item spawners, their locations and the spawn pools they spawn from. Scroll down for reference on spawn pools. -->
<ItemSpawners>
<RawItemSpawner>
<Location>
<X>11</X>
<Y>10</Y>
</Location>
<Pool>Test</Pool>
</RawItemSpawner>
<RawItemSpawner>
<Location>
<X>25</X>
<Y>10</Y>
</Location>
<Pool>Test</Pool>
</RawItemSpawner>
</ItemSpawners>
<!-- This is the list of structure spawners. Structure spawners are basically instructions on how to randomly spawn a set of structures onto different tiles of the map. -->
<StructureSpawners>
<!-- The first spawner in this list randomly spawns Rock structures on Sand tiles. The Amount field determines how many tries the spawning should have. -->
<RawStructureSpawner>
<ValidPositions>
<string>Sand</string>
</ValidPositions>
<Structure>Rock</Structure>
<Amount>40</Amount>
</RawStructureSpawner>
<RawStructureSpawner>
<ValidPositions>
<string>Grass</string>
</ValidPositions>
<Structure>Tree</Structure>
<Amount>60</Amount>
</RawStructureSpawner>
</StructureSpawners>
<!-- These are the spawn points for the four players. When spawning, the spawn points are shuffled so that players don't spawn in the same order every time. -->
<SpawnPoints>
<Point>
<X>1</X>
<Y>8</Y>
</Point>
<Point>
<X>27</X>
<Y>4</Y>
</Point>
<Point>
<X>6</X>
<Y>18</Y>
</Point>
<Point>
<X>25</X>
<Y>15</Y>
</Point>
</SpawnPoints>
</RawMap>
```
![](docs/map.png =100%x*)
## Creating a Spawn Pool
When creating a map, the items that can spawn at any given spawn point are referenced by their Spawn Pool. A spawn pool is basically just a list of items mapped to weights, where a higher weight means that the item will spawn more often, and a lower weight means that the item will spawn less often.
Spawn pools are stored in `SpawnPools/PoolName.xml`. Note that the example file included has some more comments inside of it that explain what the different parts of the file do. Also note that, further down on this page, there is a list of the game's default spawn pools.
Below is the file for an example spawn pool with some comments that explain the structure in greater detail. To get started making your own spawn pool, it would be easiest to copy this information.
```xml
<?xml version="1.0"?>
<!-- XML header information, don't change this -->
<RawPool xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<!-- These are the entries that this spawn pool has -->
<Entries>
<!-- Each entry contains the name of an item that should be spawned, as well as the weight that item should have. Scroll down for information on what the weight value means. -->
<RawPoolEntry>
<Item>Arrow</Item>
<Weight>10</Weight>
</RawPoolEntry>
<RawPoolEntry>
<Item>Bomb</Item>
<Weight>15</Weight>
</RawPoolEntry>
<RawPoolEntry>
<Item>Sword</Item>
<Weight>15</Weight>
</RawPoolEntry>
<RawPoolEntry>
<Item>LeatherArmor</Item>
<Weight>10</Weight>
</RawPoolEntry>
<RawPoolEntry>
<Item>RubberHammer</Item>
<Weight>5</Weight>
</RawPoolEntry>
<RawPoolEntry>
<Item>Spear</Item>
<Weight>15</Weight>
</RawPoolEntry>
<RawPoolEntry>
<Item>Potion</Item>
<Weight>3</Weight>
</RawPoolEntry>
</Entries>
</RawPool>
```
## Tile Names And Colors
Each tile color is written as (R, G, B). The alpha value (A) is always 255.
- Rock (255, 255, 255)
- Grass (0, 255, 0)
- Sand (255, 255, 0)
- Water (0, 0, 255)
- DeepWater (0, 0, 96)
- Dirt (127, 51, 0)
- RockWall (0, 0, 0)
- RockWallFront (81, 81, 81)
- Lava (255, 0, 0)
- Snow (208, 219, 221)
- Ice (96, 96, 255)
- FallGrass (156, 98, 43)
## Structure Names
- Rock
- Tree (automatically changes to snow or fall tree based on the tile below)
- Torch
- Cactus
- GrassTuft (automatically generates on grass as decoration)
- Flower (automatically generates on grass as decoration)
- Vine
- JungleTree
## Item Names
- Bomb
- Sword
- Arrow
- Wall
- RubberHammer
- Pickaxe
- Dynamite
- Spear
- Potion
- FlameThrower
- LeatherArmor
- Feather
- SpeedBoots
- Torch
- Shield
- AntiSlipBoots
## Default Spawn Pools
- All (Most of the game's items)
- Low (Items that are considered lower tier)
- Cave (All items except long-range weapons like arrows)
- CaveLow (Cave items that are considered lower tier)
- Lighting (Lighting items like torches)
- Pickaxe (Items to destroy rocks with like pickaxes, bombs and dynamite)
- Ice (Most of the game's items, including flame thrower, anti-slip boots and other ice stuff)
## Music Themes
- Lava
- Beach
- Cold
- Forest
- Desert

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -1,161 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Foe Frenzy</title>
<meta name="author" content="Ellpeck">
<meta name="description" content="A fast-paced fighting game where you battle up to three of your friends with random, short-lasting items">
<meta name="keywords" content="Ellpeck, Foe Frenzy, Steam, Discord, itch, itch.io, Battle, 2d, Top Down, Pixelart, MonoGame, Fighting, Game">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto">
<link rel="stylesheet" href="style.css">
<link rel="icon" href="favicon.ico">
<meta property="og:title" content="Foe Frenzy">
<meta property="og:description" content="A fast-paced fighting game where you battle up to three of your friends with random, short-lasting items">
<meta property="og:image" content="https://www.ellpeck.de/foefrenzy/media/logo.png">
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@Ellpeck">
<meta name="twitter:creator" content="@Ellpeck">
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-150032076-2"></script>
<script>
window.dataLayer = window.dataLayer || [];
let gtag = function () {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'UA-150032076-2');
</script>
</head>
<body>
<div class="main rounded">
<img src="media/banner.png" class="banner rounded" alt="Foe Frenzy Logo">
<p>Foe Frenzy is a fast-paced fighting game where you battle up to three of your friends with random, short-lasting items in an attempt to be the last survivor.</p>
<h1>Get the Game</h1>
<p>You can buy Foe Frenzy for <strong>$6.99</strong> on any of the following platforms.</p>
<div class="centered">
<a class="btn btn-success store-button" role="button" href="https://store.steampowered.com/app/1194170/">
<img src="media/steam.png" class="store-img" alt="Buy on Steam">
</a>
<a class="btn btn-success store-button" role="button" href="https://discordapp.com/store/skus/606152985181028352/">
<img src="media/discord.png" class="store-img" alt="Buy on Discord">
</a>
<a class="btn btn-success store-button" role="button" href="https://ellpeck.itch.io/foefrenzy">
<img src="media/itch.png" class="store-img" alt="Buy on itch.io">
</a>
</div>
<h1>Trailer</h1>
<div class="trailer-div">
<iframe class="trailer rounded" src="https://www.youtube.com/embed/rLac7AjWo7w" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
<h1>Screenshots</h1>
<div id="carousel" class="carousel slide" data-ride="carousel" data-interval="3000">
<ol class="carousel-indicators">
<li data-target="#carousel" data-slide-to="0" class="active"></li>
<li data-target="#carousel" data-slide-to="1"></li>
<li data-target="#carousel" data-slide-to="2"></li>
<li data-target="#carousel" data-slide-to="3"></li>
<li data-target="#carousel" data-slide-to="4"></li>
<li data-target="#carousel" data-slide-to="5"></li>
<li data-target="#carousel" data-slide-to="6"></li>
<li data-target="#carousel" data-slide-to="7"></li>
<li data-target="#carousel" data-slide-to="8"></li>
</ol>
<div class="carousel-inner rounded">
<div class="carousel-item active">
<img class="d-block w-100 rounded" src="media/screenshots/new/05-10-2020_16-40-58.png" alt="Screenshot of the main menu">
</div>
<div class="carousel-item">
<img class="d-block w-100 rounded" src="media/screenshots/new/05-10-2020_16-34-56.png" alt="Screenshot of the player selection menu">
</div>
<div class="carousel-item">
<img class="d-block w-100 rounded" src="media/screenshots/new/05-10-2020_16-36-36.png" alt="Screenshot of a cave map">
</div>
<div class="carousel-item">
<img class="d-block w-100 rounded" src="media/screenshots/new/05-10-2020_16-39-19.png" alt="Screenshot of an ice map">
</div>
<div class="carousel-item">
<img class="d-block w-100 rounded" src="media/screenshots/new/05-10-2020_16-44-47.png" alt="Screenshot of the tiebreaker screen">
</div>
<div class="carousel-item">
<img class="d-block w-100 rounded" src="media/screenshots/new/05-10-2020_16-42-20.png" alt="Screenshot of a desert map">
</div>
<div class="carousel-item">
<img class="d-block w-100 rounded" src="media/screenshots/new/05-10-2020_16-40-18.png" alt="Screenshot of the win screen">
</div>
<div class="carousel-item">
<img class="d-block w-100 rounded" src="media/screenshots/new/05-10-2020_16-35-28.png" alt="Screenshot of the map select screen">
</div>
<div class="carousel-item">
<img class="d-block w-100 rounded" src="media/screenshots/new/05-10-2020_16-41-17.png" alt="Screenshot of the achievements screen">
</div>
</div>
<a class="carousel-control-prev" href="#carousel" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#carousel" role="button" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
<h1>Features</h1>
<p>Foe Frenzy is a fast-paced fighting game where you battle up to three of your friends with random, short-lasting items in an attempt to be the last survivor.</p>
<p><img src="media/description/BattleBanner.png" alt="Battle Your Friends" width="100%"><br>Battle up to three of your friends locally or using online play. Pick up weapons, armor, healing items and more and use them to damage your enemies before they run out!</p>
<p>Play locally with up to two players on one keyboard and additional controllers, or use Steam, Discord or IP-based Multiplayer to battle over the internet!</p>
<p><img src="media/description/MapsBanner.png" alt="Play Tons of Maps" width="100%"><br>Use the terrain to your advantage on over 14 different maps. Run through hot deserts and wet swamps and chop through vines and jump down cliffs to reach the best weapons; stumble through caves where your enemies lurk in the dark and make yourself vulnerable by swimming through lakes!</p>
<p>Select the map you&#39;re best at and hope that it gets picked over the ones that your friends chose!</p>
<p><img src="media/description/ItemsBanner.png" alt="Choose Your Weapons" width="100%"><br>Fight with over 15 different items, from swords, bows and leather armor to flamethrowers and teleporters. Each item only lasts a short time, so choose carefully what move to make next!</p>
<p>Pick up random effect items that cause a disadvantage to your opponents, but have a small chance of causing you trouble too. Slow down your friends, invert their controls, or make yourself invincible!</p>
<p><img src="media/description/CustomizeBanner.png" alt="Customize" width="100%"><br>Pick the color and the look of your battler. Unlock Achievements to gain access to more character designs!</p>
<p>Once you&#39;re really good, enable expert mode for yourself for a greater challenge against your friends - lower HP, item durability and speed will make it more difficult for you to win!</p>
<p><img src="media/description/WinBanner.png" alt="Win in Time" width="100%"><br>Defeat your friends before the timer runs out to win! The results screen shows how your friends were killed, and who killed them, so make your actions count!</p>
<p>If the timer reaches a minute remaining, the game goes into Tiebreaker mode: Each player only has a single health point remaining until the end. Kill your last opponent in this exciting battle and emerge victorious!</p>
<h1>About the Project</h1>
<p>
Foe Frenzy is a game created by <a href="https://ellpeck.de">Ellpeck</a>, a student and indie game developer from Germany. It started as a small project inspired by Mario Kart and similar games, because the short-lasting battle items you can use in those games allow for fast and fun gameplay.
</p>
<p>
Development on the project started early in 2019, back when Foe Frenzy was still considered a small side project. Ellpeck made <a href="https://ellpeck.de/blog-small_projects">a blog post</a> about the process of Foe Frenzy's early creation in which he described how creating small projects is easier. After a while of developing the game, Ellpeck took a break from it due to lack of motivation. When he came back to it about two months later, he decided to turn Foe Frenzy into a bigger project, which included redesigning the art for the game, as well as the plan to turn it into a commercial project. Shortly after the rework, he made <a href="https://ellpeck.de/blog-big_projects">a second blog post</a> about this decision.
</p>
<p>With Foe Frenzy, Ellpeck hopes to have created a game that brings fun and banter to many friend groups' gaming meetups.</p>
<h1>Press</h1>
<p>
If you want to create a video or write an article about Foe Frenzy, you are more than welcome to! If you want to have it featured on this site, you can send an e-mail to <a href="mailto:me@ellpeck.de">me@ellpeck.de</a>.
</p>
<p>If you're a games journalist or a let's player and you would like to request a key for the game, you can also send an e-mail to the address above.</p>
<h2>Assets</h2>
<p>You're free to use the following images for your video or article about Foe Frenzy. Just right-click any of the images you would like to use and select <code>Save image as...</code> to save them.</p>
<div class="assets">
<img src="media/press/banner720.png" class="asset" alt="The game's banner in 16:9">
<img src="media/press/banner.png" class="asset" alt="The game's banner as a strip">
<img src="media/press/logo.png" class="asset" alt="The game's logo">
<img src="media/press/name.png" class="asset" alt="The game's logo and name">
</div>
<p><small>Note that the gray background on some of the images simply exists to make them stand out from this site's background color. It's not actually part of the images themselves.</small></p>
<p class="footer"><a href="https://git.ellpeck.de/Ellpeck/Web">&copy; 2019 Ellpeck</a> &ndash; <a href="https://ellpeck.de/#impressum">Impressum</a> &ndash; <a href="https://ellpeck.de/#privacy">Privacy</a></p>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 405 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

View file

@ -1,103 +0,0 @@
body {
position: relative;
background-image: url("media/background.png");
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
font-family: Roboto;
}
.main {
width: 50%;
display: block;
margin-top: 50px;
margin-bottom: 50px;
margin-left: auto;
margin-right: auto;
padding: 40px;
background-color: white;
}
.banner {
width: 100%;
height: auto;
margin-bottom: 20px;
}
.centered {
text-align: center;
}
.store-button {
margin: 5px;
}
.store-img {
width: 160px;
height: auto;
}
.btn {
border-radius: 0px;
}
h1 {
margin-top: 20px;
}
.trailer-div {
position: relative;
width: 100%;
height: 0;
padding-bottom: 56.25%;
}
.trailer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.assets {
text-align: center;
}
.asset {
width: 40%;
height: auto;
margin: 10px;
background-color: #BBBBBB;
}
li {
margin-top: 5px;
}
.footer {
margin-top: 40px;
}
#tutorial {
image-rendering: pixelated;
}
@media (max-width: 1200px) {
.main {
width: 80%;
}
}
@media (max-width: 768px) {
.store-img {
width: 100px;
}
}
@media (max-width: 510px) {
.main {
width: 90%;
padding: 20px;
}
}

View file

@ -1,250 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ellpeck.de</title>
<meta name="author" content="Ellpeck">
<meta name="description" content="Ellpeck's little internet place">
<meta name="keywords" content="Ellpeck, Actually Additions, Rock Bottom, Programming, Minecraft, Game Development, Nature's Aura, C#, Java, Blog, Tutorial, Foe Frenzy">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto">
<link rel="stylesheet" href="style/prettify.css">
<link rel="stylesheet" href="style/style.css">
<link rel="icon" href="favicon.ico">
<meta property="og:title" content="Ellpeck.de">
<meta property="og:description" content="Ellpeck's little internet place">
<meta property="og:image" content="https://www.ellpeck.de/res/logoSmall.png">
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@Ellpeck">
<meta name="twitter:creator" content="@Ellpeck">
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/google/code-prettify@master/loader/prettify.js"></script>
<script src="scripts/util.js"></script>
<script src="scripts/main.js"></script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-150032076-2"></script>
<script>
window.dataLayer = window.dataLayer || [];
let gtag = function () {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'UA-150032076-2');
</script>
<script data-ad-client="ca-pub-5754829579653773" async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
</head>
<body data-spy="scroll" data-target="#navbar">
<!-- Navbar -->
<nav class="navbar fixed-top navbar-expand-lg navbar-light bg-light rounded-bottom" id="navbar">
<script src="scripts/navbar.js"></script>
<!-- Navbar brand and logo -->
<a class="navbar-brand mb-0 h1" href="#">
<img src="res/logo.png" width="40" height="40" alt=""> Ellpeck.de
</a>
<!-- Responsive navbar menu opener -->
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar-content">
<span class="navbar-toggler-icon"></span>
</button>
<!-- Navbar content -->
<div class="collapse navbar-collapse" id="navbar-content">
<div class="navbar-nav mr-auto" id="nav-items">
<a class="nav-item nav-link" href="#projects">Projects</a>
<a class="nav-item nav-link" href="#social">Social</a>
<a class="nav-item nav-link" href="#about">About</a>
<a class="nav-item nav-link" href="#blog">Blog</a>
</div>
<span class="navbar-text">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="dark-mode">
<label class="custom-control-label" for="dark-mode">Dark Mode</label>
</div>
</span>
</div>
</nav>
<!-- Content -->
<div class="container main">
<!-- Cookie notification -->
<script src="scripts/cookieinfo.js"></script>
<div id="main">
<div class="no-blog">
<!-- Home -->
<div class="jumbotron">
<div class="container">
<div class="row">
<div class="col-md-auto">
<img src="res/me.png" class="rounded-circle" width="200" height="200" id="navbar-image">
</div>
<div class="col">
<h1 class="display-4" id="intro-text"></h1>
<script src="scripts/greet.js"></script>
<p class="lead">Welcome to my little website! I'm Ellpeck, a student and programmer from Germany. I do a lot of stuff, actually. My life is pretty busy.</p>
<!-- Sponsor buttons -->
<div class="sellout">
<iframe src="https://github.com/sponsors/Ellpeck/button" title="Sponsor Ellpeck" height="32" width="116" style="border: 0; margin-right: 10px;"></iframe>
<a href="https://www.patreon.com/bePatron?u=2494595" data-patreon-widget-type="become-patron-button"></a>
<script async src="https://c6.patreon.com/becomePatronButton.bundle.js"></script>
</div>
</div>
</div>
</div>
</div>
<!-- Projects -->
<a class="anchor" id="projects"></a>
<div class="list-display rounded">
<h1>Projects</h1>
<p>
Here is a list of some of the things that you might know me from. If you want to have a more in-depth look at everything I do, check out some of the sites linked in the <a href="#social">Social</a> section.
</p>
<div id="project-list">
<em>The content that should be here is dynamically generated. Please enable JavaScript if you see this.</em>
</div>
<script src="scripts/projects.js"></script>
</div>
<!-- Social -->
<a class="anchor" id="social"></a>
<div class="list-display rounded">
<h1>Social</h1>
<p>
These are other websites where you can find me and the things I do, including the pages where I publish my code and games and where I sometimes stream and upload videos. This list also includes a lot of ways to reach me.
</p>
<div class="row">
<div class="col">
<div id="social-list">
<em>The content that should be here is dynamically generated. Please enable JavaScript if you see this.</em>
</div>
Additionally, here are some miscellaneous platforms:
<ul>
<li>My Nintendo Switch friend code is <strong>SW-5281-8834-6801</strong></li>
<li>If you want to play my Mario Maker 2 levels, my ID is <strong>8BH-566-4WF</strong></li>
<li>If you play The Sims 4, you can check out my builds on the gallery by searching for <strong>Ellpeck</strong></li>
</ul>
</div>
<div class="col-md-auto" id="discord-div">
<em>The content that should be here is dynamically generated. Please enable JavaScript if you see this.</em>
</div>
</div>
<script src="scripts/social.js"></script>
</div>
<!-- About -->
<a class="anchor" id="about"></a>
<div class="list-display rounded">
<h1>About</h1>
<p>
Sometimes, some people ask me some questions about myself or my projects, so I decided to compile a list of some of the answers in a Q&A-like fashion so that I don't have to keep repeating them. If you're curious about me, this might be interesting to you!
</p>
<div id="about-list">
<em>The content that should be here is dynamically generated. Please enable JavaScript if you see this.</em>
</div>
<script src="scripts/about.js"></script>
</div>
<!-- Blog -->
<a class="anchor" id="blog"></a>
<div class="list-display rounded">
<h1>Blog</h1>
<p>
Occasionally I enjoy writing stuff. So here's some of the stuff I've written. Just click on any of the headers to open the post. Alternatively, you can subscribe to this blog using <a href="/rss.xml">RSS</a>, <a href="/atom.xml">Atom</a> or <a href="/feed.json">JSON</a>.
</p>
<div id="blog-list">
<em>The content that should be here is dynamically generated. Please enable JavaScript if you see this.</em>
</div>
<button type="button" class="btn btn-link" id="blog-archive-button">Show archived posts</button>
<div id="blog-archive">
<em>The content that should be here is dynamically generated. Please enable JavaScript if you see this.</em>
</div>
<script src="scripts/blog.js"></script>
</div>
</div>
</div>
</div>
<!-- Footer -->
<div class="footer rounded-top">
<span class="text-muted"><a href="https://git.ellpeck.de/Ellpeck/Web">&copy; 2018-2021 Ellpeck</a> &ndash; <a href="#impressum">Impressum</a> &ndash; <a href="#privacy">Privacy</a></span>
<div class="quote">
<span id="quote-text"></span>
<script src="scripts/quote.js"></script>
<img src="res/blobheart.png" id="blobheart">
</div>
</div>
<!-- Impressum -->
<div class="modal fade" id="impressum-modal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Impressum</h5>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
<p>
Julian Schubert
<br>Kastanienweg 27
<br>52074 Aachen
</p>
<p>
Telefon: 0241 45093753
<br>E-Mail: me@ellpeck.de
</p>
<p>Die obenstehende Person ist ebenfalls verantwortlich für den Inhalt (gem. § 55 Abs. 2 RStV).</p>
</div>
</div>
</div>
</div>
<!-- Privacy -->
<div class="modal fade" id="privacy-modal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Privacy</h5>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
<p>This site uses cookies to store information about your browsing activity.</p>
<p>Ellpeck.de stores the following information:</p>
<ul>
<li>A cookie named <code>dark</code> with the value <code>true</code> or <code>false</code> that stores if you have dark mode enabled</li>
<li>A cookie named <code>notification</code> with the value <code>true</code> or <code>false</code> that stores if you have already closed the cookie notification that displays at the top of the page</li>
</ul>
<p>Due to widgets and embeds, additional information will be stored by other sites. Please refer to those sites' privacy policies for more information:</p>
<ul>
<li>Google uses cookies to serve ads based on a user's prior visits to this website. You may opt out of personal advertising by visiting <a href="https://www.google.com/settings/ads">Ad Settings</a>. For more information, see <a href="http://www.google.com/policies/privacy/partners/">how Google uses data when you use its partners' sites or apps</a>.</li>
<li>Discord uses cookies for its widget embed. For more information, see <a href="https://discordapp.com/privacy">their privacy policy</a>.</li>
<li>Twitter uses cookies for its tweet embeds. For more information, see <a href="https://twitter.com/en/privacy">their privacy policy</a>.</li>
</ul>
<p>
Transparency is very important to us, and as such, if you have any doubts about the security of this website, you can view its source code <a href="https://git.ellpeck.de/Ellpeck/Web">on Gitea</a>.
<br>Keep in mind that you can also review or delete stored cookies for any site at any time in your browser's settings.
</p>
</div>
</div>
</div>
</div>
</body>
</html>

36
main/.htaccess Normal file
View file

@ -0,0 +1,36 @@
ExpiresActive On
ExpiresDefault A31536000
ExpiresByType text/html A600
ExpiresByType text/javascript A2592000
ExpiresByType application/javascript A2592000
Options +MultiViews -Indexes
ErrorDocument 404 /404.html
RewriteEngine On
RewriteBase /
# legacy redirects (for deleted stuff)
RewriteRule ^blog-(.*)$ "blog/$1" [R=301,L]
RewriteRule ^blog/(.*)\.html$ "blog/$1" [R=301,L]
RewriteRule ^minecraft-stuff/?$ "projects" [R=301,L]
RewriteRule ^mc/?$ "projects" [R=301,L]
RewriteRule ^press(/.*)?$ "https://press.ellpeck.de$1" [R=301,L]
RewriteRule ^tinylife/?$ "https://tinylifegame.com" [R=301,L]
RewriteRule ^foefrenzy/?$ "https://store.steampowered.com/app/1194170/" [R=301,L]
RewriteRule ^actaddchangelog/?$ "https://ell.lt/aachangelog" [R=301,L]
RewriteRule ^actadddownload/?$ "https://ell.lt/aadl" [R=301,L]
RewriteRule ^actaddlicense/?$ "https://ell.lt/aalicense" [R=301,L]
RewriteRule ^actadd/?$ "https://ell.lt/aa" [R=301,L]
RewriteRule ^discord/?$ "https://ell.lt/discord" [R=301,L]
RewriteRule ^wishlist/?$ "https://ell.lt/wishlist" [R=301,L,NE]
RewriteRule ^reedsy/?$ "https://ell.lt/reedsy" [R=301,L]
RewriteRule ^sims4gallery/?$ "https://ell.lt/s4gallery" [R=301,L,NE]
# anchor redirects
RewriteRule ^projects/?$ "#projects" [R=301,L,NE]
RewriteRule ^social/?$ "#social" [R=301,L,NE]
RewriteRule ^about/?$ "#about" [R=301,L,NE]
RewriteRule ^blog/?$ "#blog" [R=301,L,NE]
RewriteRule ^support/?$ "#support" [R=301,L,NE]
RewriteRule ^💸/?$ "#support" [R=301,L,NE]

View file

@ -5,12 +5,13 @@
<meta charset="UTF-8">
<title>404</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto&display=swap">
<link rel="icon" href="favicon.ico">
<style>
body {
font-family: Roboto;
font-family: Roboto, sans-serif;
font-display: swap;
background-color: #383838;
}
@ -47,7 +48,7 @@
<body>
<div class="content">
<h1>Four Oh Four</h1>
<h1>💔 Four Oh Four</h1>
<p>
There's nothing here, I'm sorry.
</p>
@ -55,18 +56,18 @@
<span id="message"></span>
<script>
const messages = [
'Maybe try a different place?',
'Do you want to know a secret?',
'This is a magical place',
'What were you trying to find?',
'Now my day is ruined',
'There\'s a place like this somewhere',
'I shall overcome this obstacle.',
'Why are you like this?',
'this is so sad alexa play despacito',
'The server is at a loss for what you were trying to find',
'oh no :(',
'The world is quiet here.',
"Maybe try a different place?",
"Do you want to know a secret?",
"This is a magical place",
"What were you trying to find?",
"Now my day is ruined",
"There's a place like this somewhere",
"I shall overcome this obstacle.",
"Why are you like this?",
"this is so sad alexa play despacito",
"The server is at a loss for what you were trying to find",
"oh no :(",
"The world is quiet here.",
"Instructions unclear, got site stuck in debug mode",
"Please fix",
"If we get this video to 400 likes, I'll put the site back up",
@ -74,7 +75,7 @@
];
let message = Math.floor(Math.random() * messages.length);
document.getElementById('message').innerHTML = '<em>' + messages[message] + '</em>';
document.getElementById("message").innerHTML = `<em>${messages[message]}</em>`;
</script>
</p>
<p class="go-home">
@ -86,4 +87,4 @@
</div>
</body>
</html>
</html>

10
main/Gemfile Normal file
View file

@ -0,0 +1,10 @@
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
gem "jekyll"
gem "webrick"
gem "jekyll-feed"
gem "jekyll-postfiles"

89
main/Gemfile.lock Normal file
View file

@ -0,0 +1,89 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.8.3)
public_suffix (>= 2.0.2, < 6.0)
colorator (1.1.0)
concurrent-ruby (1.2.2)
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0)
eventmachine (1.2.7)
eventmachine (1.2.7-x64-mingw32)
ffi (1.15.5)
forwardable-extended (2.6.0)
google-protobuf (3.22.2)
http_parser.rb (0.8.0)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
jekyll (4.3.2)
addressable (~> 2.4)
colorator (~> 1.0)
em-websocket (~> 0.5)
i18n (~> 1.0)
jekyll-sass-converter (>= 2.0, < 4.0)
jekyll-watch (~> 2.0)
kramdown (~> 2.3, >= 2.3.1)
kramdown-parser-gfm (~> 1.0)
liquid (~> 4.0)
mercenary (>= 0.3.6, < 0.5)
pathutil (~> 0.9)
rouge (>= 3.0, < 5.0)
safe_yaml (~> 1.0)
terminal-table (>= 1.8, < 4.0)
webrick (~> 1.7)
jekyll-feed (0.17.0)
jekyll (>= 3.7, < 5.0)
jekyll-postfiles (3.1.0)
jekyll (>= 3.8.6, < 5)
jekyll-sass-converter (3.0.0)
sass-embedded (~> 1.54)
jekyll-watch (2.2.1)
listen (~> 3.0)
kramdown (2.4.0)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liquid (4.0.4)
listen (3.8.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
mercenary (0.4.0)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (5.0.1)
rake (13.1.0)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
rexml (3.2.5)
rouge (4.1.0)
safe_yaml (1.0.5)
sass-embedded (1.61.0)
google-protobuf (~> 3.21)
rake (>= 10.0.0)
sass-embedded (1.61.0-x64-mingw-ucrt)
google-protobuf (~> 3.21)
sass-embedded (1.61.0-x64-mingw32)
google-protobuf (~> 3.21)
sass-embedded (1.61.0-x86_64-linux-gnu)
google-protobuf (~> 3.21)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
unicode-display_width (2.4.2)
webrick (1.8.1)
PLATFORMS
x64-mingw-ucrt
x64-mingw32
x64-unknown
x86_64-linux
DEPENDENCIES
jekyll
jekyll-feed
jekyll-postfiles
webrick
BUNDLED WITH
2.2.22

23
main/_config.yaml Normal file
View file

@ -0,0 +1,23 @@
permalink: blog/:title/
plugins:
- jekyll-feed
- jekyll-postfiles
# markdown formatting
kramdown:
header_offset: 1
typographic_syms:
hellip: ...
mdash: ---
ndash: --
laquo: "<<"
raquo: ">>"
# atom feed information
url: https://ellpeck.de
title: Ellpeck.de
description: Ell's Little Internet Place
author: Ellpeck
feed:
posts_limit: 10000

50
main/_data/about.json Normal file
View file

@ -0,0 +1,50 @@
[
{
"q": "What are your pronouns?",
"a": "I'm assigned male at birth and quite comfortable with that, so I go by he/him most of the time. My own gender isn't really that important to me, though, and I usually also include they/them as an option."
},
{
"q": "How old are you?",
"a": "I'm <span id=\"age\"></span> years old. This automatically updates now, too, so I won't ever forget to update it again!"
},
{
"q": "Where are you from?",
"a": "I am from Neuss, Germany and I currently live in Neu-Ulm, Germany. I lived in Aachen, Germany for a while as well."
},
{
"q": "Why are you called Ellpeck?",
"a": "Well, it actually isn't as interesting of a story as some of you might hope. Long story short, when I was little (and was, apparently, very bad at English), I decided to make a YouTube channel called \"LetsPlayEveryGames.\" Shortly after, I also made a Minecraft account that I was going to call the same thing. At the time, though, there was a limit for how many characters your name could have, and so I opted for calling myself \"LPEG\" instead. When a friend of mine came along and started trying to pronounce that name, instead of saying each individual letter on its own, he started pronouncing it like a word: Ell-Peg. ...Ellpeck. I liked that pronounciation and so I stuck with the name."
},
{
"q": "What should I call you?",
"a": "In general, most of my friends online call me Ell nowadays, which is also what I go by on Twitter and Discord in terms of my display names. My boyfriend calls me Peck online, but I generally don't like it when other people do so. If we meet in real life, you can call me Ell or Julian, the latter of which is my real name."
},
{
"q": "What languages do you speak?",
"a": "I speak German, English, Java and C# fluently. I'm okay at JavaScript, TypeScript, Python and PHP, and I can just about get something to happen in C and Lua."
},
{
"q": "How do you make games?",
"a": "I usually use the .NET-based framework <a href=\"https://www.monogame.net/\">MonoGame</a> together with some libraries to make it a bit easier, including my own library, MLEM, which you can read about in the <a href=\"#projects\">Projects</a> section."
},
{
"q": "When are you updating your Minecraft mods?",
"a": "If you're asking about Actually Additions, <a href=\"https://ellpeck.de/blog/future_actually_additions\">I don't work on it anymore</a>. If you're asking about any of my other mods: Don't. I'll do it when I feel like it."
},
{
"q": "What's your job/occupation?",
"a": "I'm currently studying Computer Science at <a href=\"https://www.uni-ulm.de/en/\">Ulm University</a>. I don't have a job on the side or anything, though."
},
{
"q": "Are you for hire?",
"a": "You can commission me to make a Minecraft mod for you. You can find more information on my <a href=\"https://ellpeck.de/commissions\">commissions page</a>. For other work, you can check out my page on <a href=\"https://www.linkedin.com/in/ellpeck/\">LinkedIn</a>."
},
{
"q": "What do you use to code?",
"a": "For Java, I use IntelliJ IDEA. For C#, I use JetBrains Rider. For anything web-related, I use PhpStorm. For most of my other projects, which are usually rather small, I use Visual Studio Code."
},
{
"q": "What's your favorite programming language?",
"a": "C#."
}
]

157
main/_data/projects.json Normal file
View file

@ -0,0 +1,157 @@
[
{
"name": "🏡 Tiny Life",
"desc": "Tiny Life is a fun simulation game that tries to capture the essence of games like The Sims, but in an isometric pixelart style. It's published by Top Hat Studios, and it's currently in Steam Early Access.",
"status": "In development",
"links": [
{
"name": "Check it out",
"link": "https://tinylifegame.com"
},
{
"name": "Get it on Steam",
"link": "https://store.steampowered.com/app/1651490/Tiny_Life/"
}
],
"icon": "tiny"
},
{
"name": "💡 Actually Additions",
"desc": "Actually Additions is a rather popular Minecraft mod that I used to work on. It's become widely known in the modding community and has reached over 30 million downloads by now, which is crazy. I don't work on it anymore myself, but it's being maintained for current versions by someone else.",
"links": [
{
"name": "CurseForge page",
"link": "https://ellpeck.de/actadd"
},
{
"name": "Report an issue",
"link": "https://github.com/Ellpeck/ActuallyAdditions/issues"
},
{
"name": "Online manual",
"link": "https://ellpeck.de/actaddmanual/"
}
],
"status": "Maintained",
"icon": "aa"
},
{
"name": "🌲 Nature's Aura",
"desc": "Nature's Aura is a Minecraft mod about collecting, using and replenishing the Aura naturally present in the world to create useful devices and unique mechanics.",
"status": "Side project",
"links": [
{
"name": "CurseForge page",
"link": "https://minecraft.curseforge.com/projects/natures-aura"
},
{
"name": "Modrinth page",
"link": "https://modrinth.com/mod/natures-aura"
},
{
"name": "Report an issue",
"link": "https://github.com/Ellpeck/NaturesAura/issues"
}
],
"icon": "na"
},
{
"name": "🚋 Pretty Pipes",
"desc": "Pretty Pipes is a simple to use, all-inclusive item transport mod for Minecraft. It features simple pipes that can be upgraded using modules to accomplish much more advanced tasks.",
"status": "Side project",
"links": [
{
"name": "CurseForge page",
"link": "https://www.curseforge.com/minecraft/mc-mods/pretty-pipes"
},
{
"name": "Modrinth page",
"link": "https://modrinth.com/mod/pretty-pipes"
},
{
"name": "Report an issue",
"link": "https://github.com/Ellpeck/PrettyPipes/issues"
}
],
"icon": "pp"
},
{
"name": "🖼️ Custom Frames",
"desc": "Custom Frames is an <a href=\"https://obsidian.md/\">Obsidian</a> plugin that turns web apps into panes using iframes with custom styling. Also comes with presets for Google Keep and more.",
"links": [
{
"name": "Check it out",
"link": "https://github.com/Ellpeck/ObsidianCustomFrames"
},
{
"name": "Install it",
"link": "https://obsidian.md/plugins?id=obsidian-custom-frames"
}
],
"status": "Side project",
"icon": "obsidian"
},
{
"name": "⏱️ Super Simple Time Tracker",
"desc": "Super Simple Time Tracker is an <a href=\"https://obsidian.md/\">Obsidian</a> plugin that adds multi-purpose, easy-to-use time trackers to your notes that continue running even when you close Obsidian.",
"links": [
{
"name": "Check it out",
"link": "https://github.com/Ellpeck/ObsidianSimpleTimeTracker"
},
{
"name": "Install it",
"link": "https://obsidian.md/plugins?id=simple-time-tracker"
}
],
"status": "Side project",
"icon": "obsidian"
},
{
"name": "🕹️ MLEM",
"desc": "MLEM Library for Extending MonoGame is an addition to the game framework <a href=\"https://www.monogame.net/\">MonoGame</a> that provides extension methods, quality of life improvements and additional features like a Ui system and easy input handling.",
"links": [
{
"name": "Get it on NuGet",
"link": "https://www.nuget.org/packages?q=ellpeck+mlem"
},
{
"name": "See the website",
"link": "https://mlem.ellpeck.de/"
}
],
"status": "Side project",
"icon": "mlem"
},
{
"name": "🎟️ Touchy Tickets",
"desc": "Touchy Tickets is a fun idle game for Android that has you selling tickets with various theme park attractions. You can download it for free on the Google Play Store, as well as see its public source code.",
"links": [
{
"name": "Google Play Store",
"link": "https://ell.lt/touchytickets"
},
{
"name": "See the code",
"link": "https://git.ellpeck.de/Ellpeck/TouchyTickets"
}
],
"status": "Released",
"icon": "tt"
},
{
"name": "⚔️ Foe Frenzy",
"desc": "Foe Frenzy is a small party game where you battle up to three of your friends with random, short-lasting items. I initially released this game as local-only right before the pandemic, so it never had a chance to become popular. I'm still quite fond of it though, and you can buy it for a few bucks and even play online now, too.",
"links": [
{
"name": "Steam",
"link": "https://store.steampowered.com/app/1194170/Foe_Frenzy/"
}, {
"name": "itch.io",
"link": "https://ellpeck.itch.io/foefrenzy"
}
],
"status": "Released",
"icon": "ff"
}
]

45
main/_data/socials.json Normal file
View file

@ -0,0 +1,45 @@
[
{
"name": "Twitter",
"link": "https://twitter.com/Ellpeck"
},
{
"name": "Bluesky",
"link": "https://bsky.app/profile/ellpeck.bsky.social"
},
{
"name": "GitHub",
"link": "https://github.com/Ellpeck/",
"darkIcon": true
},
{
"name": "Forgejo",
"link": "https://git.ellpeck.de/Ellpeck"
},
{
"name": "itch.io",
"link": "https://ellpeck.itch.io/",
"darkIcon": true
},
{
"name": "NuGet",
"link": "https://www.nuget.org/profiles/Ellpeck"
},
{
"name": "Twitch",
"link": "https://twitch.tv/ellpeck"
},
{
"name": "YouTube",
"link": "https://www.youtube.com/c/ellpeck"
},
{
"name": "Instagram",
"link": "https://instagram.com/Ellopecko"
},
{
"name": "Email",
"link": "mailto:me@ellpeck.de",
"darkIcon": true
}
]

22
main/_data/support.json Normal file
View file

@ -0,0 +1,22 @@
[
{
"name": "🪙 Buy Stuff I Made",
"text": "Some of the stuff I make is directly for sale, and buying it is an easy way to support me! For example, you can buy my game Tiny Life on Steam.",
"link": "#projects"
},
{
"name": "☕ Buy me a Coffee",
"text": "Ko-fi is a great site that allows you to send me just enough money to buy a coffee. And I love those. You can also use it to send monthly payments, but the other sites in this list are usually better for that.",
"link": "https://ko-fi.com/ellpeck"
},
{
"name": "⭐ Become a Patron",
"text": "Patreon allows you to support me on a monthly basis with an amount of money of your choosing. You can also get fun rewards, like player accessories to impress your friends when using my Minecraft mods!",
"link": "https://patreon.com/Ellpeck"
},
{
"name": "💻 Sponsor on GitHub",
"text": "GitHub Sponsors is basically Patreon for programmers, and it provides a monthly support option, but it also allows you to make one-time payments. Here, you can get the same rewards as you get on Patreon.",
"link": "https://github.com/sponsors/Ellpeck"
}
]

20
main/_includes/about.html Normal file
View file

@ -0,0 +1,20 @@
<div class="list-display rounded">
<h1 id="about">💬 About</h1>
<p>
Sometimes, some people ask me some questions about myself or my projects, so I decided to compile a list of some of the answers in a Q&A-like fashion so that I don't have to keep repeating them. If you're curious about me, this might be interesting to you!
</p>
<div id="about-list">
{% for item in site.data.about %}
<p>
<strong>Q: {{ item.q }}</strong><br>
<strong>A:</strong> {{ item.a }}
</p>
{% endfor %}
</div>
<script>
let birthdayMillis = Date.UTC(1999, 5 - 1, 21);
let todayMillis = Date.now();
let ageSinceStart = new Date(todayMillis - birthdayMillis);
$("#age").html(ageSinceStart.getUTCFullYear() - 1970);
</script>
</div>

View file

@ -0,0 +1,175 @@
{% capture headingsWorkspace %}
{% comment %}
Copyright (c) 2018 Vladimir "allejo" Jimenez
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
{% endcomment %}
{% comment %}
Version 1.0.13
https://github.com/allejo/jekyll-anchor-headings
"Be the pull request you wish to see in the world." ~Ben Balter
Usage:
{% include anchor_headings.html html=content anchorBody="#" %}
Parameters:
* html (string) - the HTML of compiled markdown generated by kramdown in Jekyll
Optional Parameters:
* beforeHeading (bool) : false - Set to true if the anchor should be placed _before_ the heading's content
* headerAttrs (string) : '' - Any custom HTML attributes that will be added to the heading tag; you may NOT use `id`;
the `%heading%` and `%html_id%` placeholders are available
* anchorAttrs (string) : '' - Any custom HTML attributes that will be added to the `<a>` tag; you may NOT use `href`, `class` or `title`;
the `%heading%` and `%html_id%` placeholders are available
* anchorBody (string) : '' - The content that will be placed inside the anchor; the `%heading%` placeholder is available
* anchorClass (string) : '' - The class(es) that will be used for each anchor. Separate multiple classes with a space
* anchorTitle (string) : '' - The `title` attribute that will be used for anchors
* h_min (int) : 1 - The minimum header level to build an anchor for; any header lower than this value will be ignored
* h_max (int) : 6 - The maximum header level to build an anchor for; any header greater than this value will be ignored
* bodyPrefix (string) : '' - Anything that should be inserted inside of the heading tag _before_ its anchor and content
* bodySuffix (string) : '' - Anything that should be inserted inside of the heading tag _after_ its anchor and content
* generateId (true) : false - Set to true if a header without id should generate an id to use.
Output:
The original HTML with the addition of anchors inside of all of the h1-h6 headings.
{% endcomment %}
{% assign minHeader = include.h_min | default: 1 %}
{% assign maxHeader = include.h_max | default: 6 %}
{% assign beforeHeading = include.beforeHeading %}
{% assign headerAttrs = include.headerAttrs %}
{% assign nodes = include.html | split: '<h' %}
{% capture edited_headings %}{% endcapture %}
{% for _node in nodes %}
{% capture node %}{{ _node | strip }}{% endcapture %}
{% if node == "" %}
{% continue %}
{% endif %}
{% assign nextChar = node | replace: '"', '' | strip | slice: 0, 1 %}
{% assign headerLevel = nextChar | times: 1 %}
<!-- If the level is cast to 0, it means it's not a h1-h6 tag, so let's see if we need to fix it -->
{% if headerLevel == 0 %}
<!-- Split up the node based on closing angle brackets and get the first one. -->
{% assign firstChunk = node | split: '>' | first %}
<!-- If the first chunk does NOT contain a '<', that means we've broken another HTML tag that starts with 'h' -->
{% unless firstChunk contains '<' %}
{% capture node %}
<h{{ node }}{% endcapture %}
{% endunless %}
{% capture edited_headings %}{{ edited_headings }}{{ node }}{% endcapture %}
{% continue %}
{% endif %}
{% capture _closingTag %}</h{{ headerLevel }}>{% endcapture %}
{% assign _workspace = node | split: _closingTag %}
{% capture _hAttrToStrip %}{{ _workspace[0] | split: '>' | first }}>{% endcapture %}
{% assign header = _workspace[0] | replace: _hAttrToStrip, '' %}
{% assign escaped_header = header | strip_html | strip %}
{% assign _classWorkspace = _workspace[0] | split: 'class="' %}
{% assign _classWorkspace = _classWorkspace[1] | split: '"' %}
{% assign _html_class = _classWorkspace[0] %}
{% if _html_class contains "no_anchor" %}
{% assign skip_anchor = true %}
{% else %}
{% assign skip_anchor = false %}
{% endif %}
{% assign _idWorkspace = _workspace[0] | split: 'id="' %}
{% if _idWorkspace[1] %}
{% assign _idWorkspace = _idWorkspace[1] | split: '"' %}
{% assign html_id = _idWorkspace[0] %}
{% assign h_attrs = headerAttrs %}
{% elsif include.generateId %}
<!-- If the header did not have an id we create one. -->
{% assign html_id = escaped_header | slugify %}
{% if html_id == "" %}
{% assign html_id = false %}
{% endif %}
<!-- Append the generated id to other potential header attributes. -->
{% capture h_attrs %}{{ headerAttrs }} id="%html_id%"{% endcapture %}
{% endif %}
<!-- Build the anchor to inject for our heading -->
{% capture anchor %}{% endcapture %}
{% if skip_anchor == false and html_id and headerLevel >= minHeader and headerLevel <= maxHeader %}
{% if h_attrs %}
{% capture _hAttrToStrip %}{{ _hAttrToStrip | split: '>' | first }} {{ h_attrs | strip | replace: '%heading%', escaped_header | replace: '%html_id%', html_id }}>{% endcapture %}
{% endif %}
{% capture anchor %}href="#{{ html_id }}"{% endcapture %}
{% if include.anchorClass %}
{% capture anchor %}{{ anchor }} class="{{ include.anchorClass }}"{% endcapture %}
{% endif %}
{% if include.anchorTitle %}
{% capture anchor %}{{ anchor }} title="{{ include.anchorTitle | replace: '%heading%', escaped_header }}"{% endcapture %}
{% endif %}
{% if include.anchorAttrs %}
{% capture anchor %}{{ anchor }} {{ include.anchorAttrs | replace: '%heading%', escaped_header | replace: '%html_id%', html_id }}{% endcapture %}
{% endif %}
{% capture anchor %}<a {{ anchor }}>{{ include.anchorBody | replace: '%heading%', escaped_header | default: '' }}</a>{% endcapture %}
<!-- In order to prevent adding extra space after a heading, we'll let the 'anchor' value contain it -->
{% if beforeHeading %}
{% capture anchor %}{{ anchor }} {% endcapture %}
{% else %}
{% capture anchor %} {{ anchor }}{% endcapture %}
{% endif %}
{% endif %}
{% capture new_heading %}
<h{{ _hAttrToStrip }}
{{ include.bodyPrefix }}
{% if beforeHeading %}
{{ anchor }}{{ header }}
{% else %}
{{ header }}{{ anchor }}
{% endif %}
{{ include.bodySuffix }}
</h{{ headerLevel }}>
{% endcapture %}
<!--
If we have content after the `</hX>` tag, then we'll want to append that here so we don't lost any content.
-->
{% assign chunkCount = _workspace | size %}
{% if chunkCount > 1 %}
{% capture new_heading %}{{ new_heading }}{{ _workspace | last }}{% endcapture %}
{% endif %}
{% capture edited_headings %}{{ edited_headings }}{{ new_heading }}{% endcapture %}
{% endfor %}
{% endcapture %}{% assign headingsWorkspace = '' %}{{ edited_headings | strip }}

39
main/_includes/blog.html Normal file
View file

@ -0,0 +1,39 @@
<div class="list-display rounded">
<h1 id="blog">
<span id="blog-all"></span>
{% for tag in site.tags %}
<span id="blog-{{ tag[0] | slugify }}"></span>
{% endfor %}
📔 Blog
</h1>
<p>
This is my blog, where I post about gaming, programming and life. The featured posts are the ones you'll probably be most interested in, but you can select a different category to see every post.
</p>
<p>📫 You can also subscribe to this blog through the <a href="https://ell.lt/lists">mailing list</a> or the <a href="/feed.xml">Atom feed</a>.</p>
<div id="blog-cats">
{% include tagbtn.html tag="All" %}
{%- assign sorted = site.tags | sort -%}
{%- for tag in sorted -%}
{%- assign name = tag[0] -%}
{% include tagbtn.html tag=name %}
{%- endfor -%}
</div>
<div id="blog-list">
{% for post in site.posts %}
{% if post.archived != true %}
{% include post.html post=post %}
{% endif %}
{% endfor %}
</div>
<button type="button" class="btn btn-link" id="blog-archive-button">Show archived posts</button>
<div id="blog-archive">
{% for post in site.posts %}
{% if post.archived == true %}
{% include post.html post=post %}
{% endif %}
{% endfor %}
<em id="no-archived-posts" hidden>There are no archived posts in this category.</em>
</div>
<script src="/scripts/blog.js"></script>
</div>

View file

@ -0,0 +1,16 @@
<!-- Seasonal banner -->
<a id="banner" class="rounded-top" hidden></a>
<script src="/scripts/banner.js"></script>
<!-- Footer -->
<div class="footer rounded-top">
<a href="https://git.ellpeck.de/Ellpeck/Web">&copy; 2018-2024 Ellpeck</a> &ndash; <a href="/impressum">Impressum</a> &ndash; <a href="/privacy">Privacy</a> &ndash; <a href="https://status.ellpeck.de">Status</a>
<div class="quote">
<span id="quote-text"></span>
<script src="/scripts/quote.js"></script>
<img src="/res/blobheart.png" id="blobheart" alt="A blob emoji holding a heart">
<script>
$("#blobheart").on("click", () => $("#navbar-image").attr("src", "res/heart.jpeg"));
</script>
</div>
</div>

View file

@ -0,0 +1,22 @@
{% assign tag = page.tags[0] %}
{% assign posts = site.tags[tag] | where_exp: "post", "post.archived != true" %}
{% for post in posts %}
{% if post.url != page.url %}
{% continue %}
{% endif %}
{% if forloop.last == false %}
{% assign prev = forloop.index0 | plus: 1 %}
{% endif %}
{% if forloop.first == false %}
{% assign next = forloop.index0 | minus: 1 %}
{% endif %}
{% break %}
{% endfor %}
<a class="nav-item nav-link" href="../../#blog-{{ tag | slugify }}">🏠 Back to Main Page</a>
{% if prev %}
<a class="nav-item nav-link" href="{{ posts[prev].url }}">⏮️ Previous in {{ tag }}</a>
{% endif %}
{% if next %}
<a class="nav-item nav-link" href="{{ posts[next].url }}">⏭️ Next in {{ tag }}</a>
{% endif %}

View file

@ -0,0 +1,5 @@
<a class="nav-item nav-link" href="#projects">💻 Projects</a>
<a class="nav-item nav-link" href="#social">🔗 Social</a>
<a class="nav-item nav-link" href="#about">💬 About</a>
<a class="nav-item nav-link" href="#blog">📔 Blog</a>
<a class="nav-item nav-link" href="#support">💸 Support Me</a>

View file

@ -0,0 +1 @@
<a class="nav-item nav-link" href="/">🏠 Back to Main Page</a>

View file

@ -0,0 +1,48 @@
<nav class="navbar fixed-top navbar-expand-lg navbar-light bg-light rounded-bottom" id="navbar">
<script>
if (dark) {
let navbar = $("#navbar");
navbar.removeClass("navbar-light bg-light");
navbar.addClass("navbar-dark bg-dark");
}
</script>
<!-- Navbar brand and logo -->
<a class="navbar-brand mb-0 h1" href="#">
<img src="/res/logo.png" width="40" height="40" alt="A pixelart version of Ellpeck"> Ellpeck.de
</a>
<!-- Responsive navbar menu opener -->
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar-content" aria-label="Navbar toggler">
<span class="navbar-toggler-icon"></span>
</button>
<!-- Navbar content -->
<div class="collapse navbar-collapse" id="navbar-content">
<div class="navbar-nav mr-auto" id="nav-items">
{% if page.nav %}
{% include {{ page.nav }} %}
{% elsif layout.nav %}
{% include {{ layout.nav }} %}
{% endif %}
</div>
<span class="navbar-text custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="dark-mode">
<label class="custom-control-label" for="dark-mode">🌙 Dark Mode</label>
<script>
let mode = $("#dark-mode");
mode.prop("checked", dark);
mode.on("click", function () {
localStorage.setItem("dark", $(this).prop("checked"));
location.hash = "";
location.reload();
});
</script>
</span>
<script>
$(".navbar-collapse a").on("click", () => $(".navbar-collapse").collapse("hide"));
</script>
</div>
</nav>

7
main/_includes/post.html Normal file
View file

@ -0,0 +1,7 @@
<div class="card bg-light blog-entry rounded-0 {% for tag in include.post.tags %} blog-tag-{{ tag | slugify }} {% endfor %}">
<div class="card-body">
<span class="blog-meta text-muted">{{ include.post.tags | join: ", " }}<br>{{ include.post.date | date_to_string }}</span>
<h4 class="card-title"><a class="title-button" href="{{ include.post.url }}">{{ include.post.title }}</a></h4>
<div class="card-text">{{ include.post.description }}</div>
</div>
</div>

View file

@ -0,0 +1,36 @@
<div class="list-display rounded">
<h1 id="projects">💻 Projects</h1>
<p>
Here is a list of some of my bigger projects as well as some things you might know me from.<br>
For a more exhaustive list of my projects, you can also check out some of the sites in the <a href="#social">Social</a> section.
</p>
<div id="project-list">
{% for item in site.data.projects %}
<div class="card bg-light project rounded-0">
<div class="card-body">
<img class="project-image" src="res/projects/{{ item.icon }}.png" alt="">
{% if item.status %}
<span class="text-muted project-status">{{ item.status }}</span>
{% endif %}
<h4 class="card-title">{{ item.name }}</h4>
<p class="card-text">{{ item.desc }}</p>
{%- for link in item.links -%}
<a href="{{ link.link }}" class="card-link btn rounded-0 btn-outline-dark">{{ link.name }}</a>
{%- endfor -%}
</div>
</div>
{% endfor %}
</div>
<script>
if (dark) {
$("#project-list").find(".btn-outline-dark").each(function () {
let e = $(this);
e.removeClass("btn-outline-dark");
e.addClass("btn-outline-light");
});
}
</script>
</div>

View file

@ -0,0 +1,51 @@
<div class="list-display rounded">
<h1 id="social">🔗 Social</h1>
<p>
These are other websites where you can find me and the things I do, including the pages where I publish my code and games and where I sometimes stream and upload videos. This list also includes a lot of ways to reach me.
</p>
<p>⚠️ Some of these sites may contain mature content.</p>
<div class="row">
<div class="col">
<div id="social-list">
{% for item in site.data.socials %}
<a class="btn btn-light social-button rounded-0" href="{{ item.link }}">
<img class="social-image social-image-light" src="res/social/{{ item.name | downcase }}.png" alt="Social icon">
{% if item.darkIcon %}
{% assign dark = item.name | downcase | append: "_dark" %}
{% else %}
{% assign dark = item.name | downcase %}
{% endif %}
<img class="social-image social-image-dark" src="res/social/{{ dark }}.png" alt="Social icon" hidden>
{{ item.name }}
</a>
{% endfor %}
</div>
Additionally, here are some miscellaneous socials:
<ul>
<li>I post most of my Sims 4 builds on <a href="https://ellpeck.de/sims4gallery">the gallery</a> as <strong>Ellpeck</strong>. I also post screenshots on my <a href="https://www.instagram.com/ellssimsbuilds/">Simstagram</a>.</li>
<li>My Nintendo Switch friend code is <strong>SW-5281-8834-6801</strong>.</li>
</ul>
</div>
<div class="col-md-auto" id="discord-div">
<iframe id="discord-widget" title="Ellpeck's Discord server" width="300" height="500" allowtransparency="true" frameborder="0"></iframe>
<span class="discord-landing">Or check out <a href="https://link.ellpeck.de/discordweb">the landing page</a></span>
</div>
<script>
if (dark) {
$(".social-image-light").attr("hidden", true);
$(".social-image-dark").attr("hidden", false);
$("#social-list").find(".btn-light").each(function () {
let e = $(this);
e.removeClass("btn-light");
e.addClass("btn-dark");
});
}
let theme = dark ? "dark" : "light";
$("#discord-widget").attr("src", `https://discordapp.com/widget?id=181435613147430913&theme=${theme}`);
</script>
</div>
</div>

View file

@ -0,0 +1,25 @@
<div class="list-display rounded">
<h1 id="support">💸 Support Me</h1>
<p>
Most of the projects you know me from, I work on in my free time, but by supporting me, you help out with all of my projects equally. As I'm a student, a bit of extra cash is always helpful for me, and it feels especially good when it comes from something that I enjoy doing a lot. ❤️
</p>
<div id="support-list">
{% for item in site.data.support %}
<div class="card bg-light support-entry rounded-0">
<div class="card-body">
<h4 class="card-title"><a class="title-button" href="{{ item.link }}">{{ item.name }}</a></h4>
<p class="card-text">{{ item.text }}</p>
</div>
</div>
{% endfor %}
</div>
<script>
if (dark) {
$("#support-list").find(".btn-outline-dark").each(function () {
let e = $(this);
e.removeClass("btn-outline-dark");
e.addClass("btn-outline-light");
});
}
</script>
</div>

View file

@ -0,0 +1 @@
<button type="button" class="btn btn-link blog-cat-button" id="{{ include.tag | slugify }}">{{ include.tag }}</button>

79
main/_layouts/blog.html Normal file
View file

@ -0,0 +1,79 @@
---
layout: default
nav: nav/blognav.html
---
<style>
html {
scroll-padding-top: 70px;
}
h1 .anchor-heading, h2 .anchor-heading, h3 .anchor-heading, h4 .anchor-heading, h5 .anchor-heading, h6 .anchor-heading {
visibility: hidden;
}
h1:hover .anchor-heading, h2:hover .anchor-heading, h3:hover .anchor-heading, h4:hover .anchor-heading, h5:hover .anchor-heading, h6:hover .anchor-heading {
visibility: visible;
}
</style>
{% if page.book %}
<link rel="stylesheet" href="/style/book.css">
{% else %}
<script>
let style = dark ? "monokai" : "friendly";
$("head").append($("<link/>", {
rel: "stylesheet",
href: `https://cdn.jsdelivr.net/gh/richleland/pygments-css@master/${style}.css`
}));
</script>
{% endif %}
<div class="list-display rounded">
<div class="blog-isolated">
<h1>{{ page.title }}</h1>
{% if page.archived %}
<p>🧓 This post has been archived.</p>
{% endif %}
{% if page.mature %}
<p>⚠️ This post contains mature content.</p>
{% endif %}
{% if page.reedsy %}
<p>📘 This story was inspired by a Reedsy Prompt and submitted to their competition. As such, it has also been published on <a href="{{ page.reedsy }}">their website</a>.</p>
{% endif %}
<div class="post-content">
{% include anchor_headings.html html=content anchorBody="#" anchorClass="anchor-heading" %}
</div>
<span class="text-muted project-status blog-isolated-status">{{ page.date | date_to_string }}</span>
{% if page.discuss %}
🧵 <a href="{{ page.discuss }}" class="blog-discuss">Discuss this post</a>
{% endif %}
</div>
</div>
<div class="alert alert-success mail-alert">
<h3>📫 Subscribe</h3>
<p>If you liked this blog post, you can subscribe to the mailing list to be notified of new posts and occasional special content. I promise not to send too many emails, but you can always unsubscribe easily, too.</p>
<form method="post" action="https://lists.ellpeck.de/subscription/form" class="listmonk-form text-center">
<div>
<input type="hidden" name="nonce">
<p><input type="email" name="email" required placeholder="E-mail"></p>
<p><input type="text" name="name" placeholder="Name (optional)"></p>
<p>
<input id="d3ee1" type="checkbox" name="l" checked value="d3ee16aa-f47a-43bd-a0d6-759d108a5650"/>
<label for="d3ee1">📔 Ellpeck's Blog</label>
</p>
<p><input type="submit" value="Subscribe"></p>
</div>
</form>
<p>📰 Alternatively, you can also subscribe to this blog through the <a href="/feed.xml">Atom feed</a>.</p>
</div>

View file

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script>
// redirect legacy anchors
if (/^#privacy\/?$/.test(location.hash))
location.href = "/privacy";
if (/^#impressum\/?$/.test(location.hash))
location.href = "/impressum";
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ page.title }}</title>
<meta name="author" content="Ellpeck">
<meta name="description" content="{{ page.description }}">
<meta name="keywords" content="Ellpeck, Actually Additions, Programming, Minecraft, Game Development, Nature's Aura, C#, Java, Blog, Tutorial, Pretty Pipes, Tiny Life, Ellpeck Games">
<meta property="og:title" content="{{ page.title }}">
<meta property="og:description" content="{{ page.description }}">
<meta property="og:image" content="https://ellpeck.de/res/logoSmall.png">
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@Ellpeck">
<meta name="twitter:creator" content="@Ellpeck">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
const darkCookie = localStorage.getItem("dark");
const dark = darkCookie === null ? window.matchMedia("(prefers-color-scheme: dark)").matches : darkCookie === "true";
</script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto&display=swap">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto+Slab&display=swap">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=JetBrains+Mono&display=swap">
<link rel="stylesheet" href="/style/style.css">
<link rel="stylesheet" href="/style/dark.css">
<link rel="icon" href="/favicon.ico">
</head>
<body data-spy="scroll" data-target="#navbar" data-offset="140">
<script>
if (dark)
$("body").addClass("dark-mode");
</script>
{% include nav/navbar.html %}
<!-- Content -->
<div class="container main">
<!-- Cookie notification -->
<div id="cookieinfo"></div>
<script src="/scripts/cookieinfo.js"></script>
<div id="main">
{{ content }}
</div>
</div>
{% include footer.html %}
</body>
</html>

View file

@ -1,6 +1,15 @@
So I've been wanting to make a blog for a while, but never found the motivation to do so. Especially with all of the blog softwares out there, it was hard to figure out which one to use to make it fit this website and its design nicely.
I didn't really want to make a whole different page just for the blog because it would kind of throw the design off, so I took some inspiration from [Vazkii's blog](https://vazkii.us/#blog) and made mine work in a similar fashion. This still made it work with the single page design.
Now because I'm not good with PHP at all, I actually made this entire thing using only JavaScript. It's.. a bit of a mess, honestly, but it works.
---
layout: blog
title: ✨ Blogs are Cool, I Think
description: The first post and how I created it
tags: [Miscellaneous]
discuss: https://twitter.com/Ellpeck/status/1096937184601538566
archived: true
---
So I've been wanting to make a blog for a while, but never found the motivation to do so. Especially with all of the blog softwares out there, it was hard to figure out which one to use to make it fit this website and its design nicely.
I didn't really want to make a whole different page just for the blog because it would kind of throw the design off, so I took some inspiration from [Vazkii's blog](https://vazkii.us/#blog) and made mine work in a similar fashion. This still made it work with the single page design.
Now because I'm not good with PHP at all, I actually made this entire thing using only JavaScript. It's.. a bit of a mess, honestly, but it works.
So here it is. Expect me to, very occasionally, do a post about one or the other random thing. *Enjoy!*

View file

@ -1,44 +1,52 @@
So... for the last couple of years, the main thing I've been doing in my free time is game development and modding Minecraft, but the latter far more often and more consistently. I've learned a lot while doing this, but not only when it comes to programming and game development, but also when it comes to a lot of other things.
So in this post, I want to introduce you to what's so great about Minecraft modding and, honestly, why you should try doing it as well.
# Programming Experience
Now, first and foremost, and I think this is definitely one of the most obvious point: It improves your programming experience by quite a bit. Both in the "understanding the language" department, but also, it helps you to do a couple of things that you'll need if you want to deal with anything that other people have made:
- Understanding someone else's codebase and learning to navigate code that someone else has made, both documented and undocumented
- The beauty of learning how annoying it is to see code that's made in a way that makes it *so unbelievably hard* to expand it, to build on it, and learning that, when making your own code, you most definitely shouldn't do it like that.
- Learning to make something fit well with something other people have made. I'm sure this is also a useful skill to have in a job that includes team efforts: The same way a paper should have a unified style, it's also not very nice to have high resolution textures in a mod for a game that has a very primitive artstyle.
# Easily Creating Things
When creating your own game, I've personally found that it's pretty hard to get it off the ground at the start, because all you can see for the first couple of days are missing textures, placeholders, a missing main menu, janky controls and so on. When you're making a mod for any game, but especially for Minecraft, you can create a single, small feature and then instantly start using it together with all of the other features that the game already provides for you.
It's so much easier to get excited and satisfied about something you've made when it doesn't take ages for it to actually look good in its intended environment - making a mod for a game is perfect for that.
# Meeting Amazing People
This is something that definitely comes a bit later down the line, at least it did for me, but it's also something that's been really, really important to me. Through modding Minecraft, and especially by making [Actually Additions](https://minecraft.curseforge.com/projects/actually-additions), I've met so many amazing people and made a huge amount of new friends.
And not only that, I've also met a lot of people that I really look(ed) up to and respect(ed), and then noticed how down to earth and nice they actually are (because after all, they're also just people): Direwolf20, Vazkii and so many other people became my friends because it's easy to get along well with someone that shares the same interests as you, especially when it's an interest as passionate as the Minecraft Modding community.
# Being Validated by Thousands
This is one of the most important bits for me, honestly. I have pretty low self esteem (as a lot of introverted people on the internet do, I imagine), and it's hard for me to be proud of something that I've created.
But through the mods I've made, especially Actually Additions and recently also [Nature's Aura](https://minecraft.curseforge.com/projects/natures-aura), I've been shown by so many people, both friends and complete strangers, that the stuff I make seems to be worth quite a lot to quite a lot of people, and that they're passionate about my work, some maybe even more passionate than I am about it myself.
It feels so good to make something and to see people downloading it, people playing with it, people making videos about it and saying how nice they think it is. Trying to be validated isn't "seeking attention", it's good for you, because it makes you feel like you're worth a lot. Because you are.
# Learning to Deal With Criticism
Also a big thing for me for sure. Because of my previously mentioned low self esteem, it's been pretty hard for me to accept and deal with people saying that they dislike something that I've created.
But in the Minecraft modding community, oh boy, it's a *huge* thing. The first thing you'll get to hear about any new mod you publish is how it's "*totally a ripoff of Thaumcraft and Botania*" and "*this is just Extra Utilities, but worse*", and so on, and so forth. And.. at first, it can be quite demotivating, to say the least, because it sucks that people compare your thing to someone else's thing, despite the fact that you put at least an equal amount of work in it, and despite the fact that it deserves the same amount of respect as that other thing.
But that's just the way people are, and sooner or later, you have to learn to accept that. And you will. Positive people will still be there, and the stuff you've made will still be loved by a lot of them.
And it's not all bad either, in fact, the opposite is true: There are a *lot* of people that give you constructive criticism: "*I enjoyed this, but I think x, y and z could be changed and it would be even better.*" If you're the kind of person that says this, then you're a winner, because this helps not only the thing you're commenting on, but also the person that's made the thing: Now they know what you like, what you don't like, but most importantly: *How to fix it for you*. And the best thing is: You as the creator don't even have to listen to them if you disagree! They'll probably understand, because they're nice enough not to be dicks like the people I talked about before.
So keep on going, no matter what people say, because you're probably awesome.
# Basically No Obligations
When making a game, or a "real" product in general, a lot of the time, you're bound to certain obligations: Either it's legal ones, or maybe you've done a Kickstarter, maybe the thing you make actually costs you quite a bit of money because you have a whole development team, stuff like that.
Well with Minecraft modding, the only thing you have to deal with is people asking you when you'll *finally* update to the next version. But if you're sick of that, or sick of anything else, you can just stop, take a break, and do something else entirely for a while.
People might judge, but the important thing is: They don't have a right to judge, because you can do whatever you want. It's a hobby thing, so it's your thing.
# So yea,
this is all of the stuff I could think of that inspires me to keep going with Minecraft Modding. It's been a really awesome journey so far and I'm excited to keep going, to create more things and to see more things that other people created.
Thanks for reading! <3
---
layout: blog
title: ⚙️ Why You Should Mod Minecraft
description: About what makes Minecraft modding great and why you should probably try it if you enjoy programming
tags: [Minecraft, Programming]
discuss: https://twitter.com/Ellpeck/status/1097177774337462272
---
So... for the last couple of years, the main thing I've been doing in my free time is game development and modding Minecraft, but the latter far more often and more consistently. I've learned a lot while doing this, but not only when it comes to programming and game development, but also when it comes to a lot of other things.
So in this post, I want to introduce you to what's so great about Minecraft modding and, honestly, why you should try doing it as well.
# Programming Experience
Now, first and foremost, and I think this is definitely one of the most obvious point: It improves your programming experience by quite a bit. Both in the "understanding the language" department, but also, it helps you to do a couple of things that you'll need if you want to deal with anything that other people have made:
- Understanding someone else's codebase and learning to navigate code that someone else has made, both documented and undocumented
- The beauty of learning how annoying it is to see code that's made in a way that makes it *so unbelievably hard* to expand it, to build on it, and learning that, when making your own code, you most definitely shouldn't do it like that.
- Learning to make something fit well with something other people have made. I'm sure this is also a useful skill to have in a job that includes team efforts: The same way a paper should have a unified style, it's also not very nice to have high resolution textures in a mod for a game that has a very primitive artstyle.
# Easily Creating Things
When creating your own game, I've personally found that it's pretty hard to get it off the ground at the start, because all you can see for the first couple of days are missing textures, placeholders, a missing main menu, janky controls and so on. When you're making a mod for any game, but especially for Minecraft, you can create a single, small feature and then instantly start using it together with all of the other features that the game already provides for you.
It's so much easier to get excited and satisfied about something you've made when it doesn't take ages for it to actually look good in its intended environment - making a mod for a game is perfect for that.
# Meeting Amazing People
This is something that definitely comes a bit later down the line, at least it did for me, but it's also something that's been really, really important to me. Through modding Minecraft, and especially by making [Actually Additions](https://minecraft.curseforge.com/projects/actually-additions), I've met so many amazing people and made a huge amount of new friends.
And not only that, I've also met a lot of people that I really look(ed) up to and respect(ed), and then noticed how down to earth and nice they actually are (because after all, they're also just people): Direwolf20, Vazkii and so many other people became my friends because it's easy to get along well with someone that shares the same interests as you, especially when it's an interest as passionate as the Minecraft Modding community.
# Being Validated by Thousands
This is one of the most important bits for me, honestly. I have pretty low self esteem (as a lot of introverted people on the internet do, I imagine), and it's hard for me to be proud of something that I've created.
But through the mods I've made, especially Actually Additions and recently also [Nature's Aura](https://minecraft.curseforge.com/projects/natures-aura), I've been shown by so many people, both friends and complete strangers, that the stuff I make seems to be worth quite a lot to quite a lot of people, and that they're passionate about my work, some maybe even more passionate than I am about it myself.
It feels so good to make something and to see people downloading it, people playing with it, people making videos about it and saying how nice they think it is. Trying to be validated isn't "seeking attention", it's good for you, because it makes you feel like you're worth a lot. Because you are.
# Learning to Deal With Criticism
Also a big thing for me for sure. Because of my previously mentioned low self esteem, it's been pretty hard for me to accept and deal with people saying that they dislike something that I've created.
But in the Minecraft modding community, oh boy, it's a *huge* thing. The first thing you'll get to hear about any new mod you publish is how it's "*totally a ripoff of Thaumcraft and Botania*" and "*this is just Extra Utilities, but worse*", and so on, and so forth. And.. at first, it can be quite demotivating, to say the least, because it sucks that people compare your thing to someone else's thing, despite the fact that you put at least an equal amount of work in it, and despite the fact that it deserves the same amount of respect as that other thing.
But that's just the way people are, and sooner or later, you have to learn to accept that. And you will. Positive people will still be there, and the stuff you've made will still be loved by a lot of them.
And it's not all bad either, in fact, the opposite is true: There are a *lot* of people that give you constructive criticism: "*I enjoyed this, but I think x, y and z could be changed and it would be even better.*" If you're the kind of person that says this, then you're a winner, because this helps not only the thing you're commenting on, but also the person that's made the thing: Now they know what you like, what you don't like, but most importantly: *How to fix it for you*. And the best thing is: You as the creator don't even have to listen to them if you disagree! They'll probably understand, because they're nice enough not to be dicks like the people I talked about before.
So keep on going, no matter what people say, because you're probably awesome.
# Basically No Obligations
When making a game, or a "real" product in general, a lot of the time, you're bound to certain obligations: Either it's legal ones, or maybe you've done a Kickstarter, maybe the thing you make actually costs you quite a bit of money because you have a whole development team, stuff like that.
Well with Minecraft modding, the only thing you have to deal with is people asking you when you'll *finally* update to the next version. But if you're sick of that, or sick of anything else, you can just stop, take a break, and do something else entirely for a while.
People might judge, but the important thing is: They don't have a right to judge, because you can do whatever you want. It's a hobby thing, so it's your thing.
# So yea,
this is all of the stuff I could think of that inspires me to keep going with Minecraft Modding. It's been a really awesome journey so far and I'm excited to keep going, to create more things and to see more things that other people created.
Thanks for reading! <3

View file

@ -1,65 +1,74 @@
As originally a java kid, I've never had much trouble with making any of my programs work on Windows, Linux and Mac. With Java, all you have to do is make a jar and then tell people to install Java on their machine. That's it. But oh boy, am I getting hit in the face hard now that I'm making a game with MonoGame and .NET Framework.
# The story, with all its horrible parts
Imagine you're me. Imagine you're just happily making a game and eventually you decide that you want to distribute it to testers.
## The documentation
So you check the documentation of the framework you're using: MonoGame, in my case. It advertises itself as being amazing at cross-platform, so I look around to find a simple tutorial on what to do to get my game working on Mac and Linux.
Mono is where it's at. Okay. An open source implementation of .NET (or something like that) that works on Linux and Mac. Great! Now I just need to find out how to easily package that with my game, so that people on those operating systems don't have to install it on their own.
## Mono Kickstart
So there's a project on GitHub with a MonoGame fork (meaning they should work together flawlessly!) called Mono Kickstart. It says that you can just add the files to your project, rename a couple of them, change one or two lines in a bash script and then your game will run smoothly on Linux and Mac without having to install Mono.
Yea, no. Some random issue about `System.Runtime.dll` not being found, no trace of the issue online, no tutorials anywhere, the Kickstart repository doesn't have an Issues tab for some reason. Dead end.
## mkbundle
Searching some more online, I found a tool contained in Mono called mkbundle, that, apparently, you can use to bundle standalone applications for Windows, Linux and Mac by just running a simple command. Seems easy enough.
Yea, no. Installing Mono on Windows is a huge pain. mkbundle's target downloading doesn't work on Windows, so I have to go into some obscure folders, download some obscure zips, rename them, move them around until it finally works.
Hah. For some reason, mkbundle now requires me to install Visual Studio. So I install their commandline build tools that, after asking in my Discord, are apparently what mkbundle wants to use for something.
So then it finally works, and I have a... yea, no. Apparently now, mkbundle can't find `System.Runtime.dll`, just like Mono Kickstart couldn't before. So I search around in some folders and notice that that dll is in a sub-folder instead of the main folder it tries to look for it in, so I tell mkbundle that it's in that subfolder through a long and annoying addition to the command, and now it finally works.
Yea, just kidding. Now it crashes because it can't find `stdint.h`. It's neither telling me where it's trying to look for it, nor why it even needs it, what it is or really anything of use at all. Dead end.
## .NET Core
After some more looking, I find that .NET Core is basically a better version of .NET Framework (it's actually a lot different, but that doesn't matter in the scope of this post) that actually runs on multiple platforms natively.
So I go through the process of upgrading my game project from .NET Framework to .NET Core, and everything finally works. I can really easily use my IDE (Rider) to publish packaged builds of the game for Windows, Linux and Mac that don't even need the user to have .NET Core installed.
Except that it doesn't run on my Laptop for some strange reason. It runs fine on my PC, and it runs fine on my boyfriend's computer and his Linux VM, but my Laptop has some obscure issue about MonoGame not being able to load the shaders it requires. There are also no traces of the issue online, and after some looking around, I realize that MonoGame's .NET Core implementation is, according to the repository, just a hack that also hasn't been updated in over a year. Dead end.
(Also, I had a weird issue where, with the newest version of MonoGame for .NET Core, my game would randomly stutter, and while the MonoGame Discord was unhelpful and one person was frankly unreasonably rude in my opinion, I found out that it isn't the fault of my code (after plenty of profiling) and that the issue is easily fixed by downgrading to an earlier version. Why? Who even knows at this point.)
## So I guess I'll just force the user to install Mono themselves then
I tested around a bit after going back to .NET Framework and it turns out that installing Mono on Linux isn't actually that bad, and after you installed it, you don't even have to run a special command to get the game to run. All you have to do is go into a terminal and run the game's normal exe, and apparently mono figures itself out without needing to specifically call it. So that's great!
Except that, on Mac, it seems to be a whole nother story altogether. The one person I asked to try it on Mac installed Mono, but then the system didn't seem to realize that it was actually installed, as the `mono` command didn't work, and just running the game's exe directly also didn't work.
## Other stuff I tried
Because it's so fun, here's a list of other stuff that I tried to use:
* Some obscure NuGet package that's supposed to make an easy `msbuild` command to bundle the game. Also didn't work, but at least I submitted an issue to the project's GitHub repository. We'll see what happens, maybe this will be the saving grace.
* Some obscure program from three years ago, with outdated packages and outdated dependencies, that was supposed to make it really easy to bundle projects for all operating systems. It had the same `System.Runtime.dll` issue, but because it was so outdated anyway, I didn't even bother investigating why.
## Conclusion?
Honestly, I don't know what to do at this point. After finding out how horrible it is to make anything work cross-platform, I'm genuinely considering just rewriting the entire game using libgdx (which is a game framework for Java), or something.
There's no real conclusion to this, other than that it took me a whole lot of my time and energy and that it made me buy and eat way too many tubs of Ben & Jerry's ice cream, which is way too expensive, by the way.
# Also, the Multiplayer debacle
As a little side note, I recently added Online Multiplayer to the game in question, using the C# networking library Lidgren.
Its UPnP implementation doesn't seem to be with my router (or my something else), so I have to manually forward ports as if it were 2003.
Also, one of the testers has a completely different issue about incomplete packets that I have no idea about, and frankly, my energy to work on anything is so low at this point that I might as well just not care about it.
# Conclusion
If you're thinking about becoming an indie developer, you'll have to deal with this sort of stuff. And honestly, it's going to bring you down. Quite a lot.
A couple weeks ago, I picked up this project again after taking a long break from it out of lack of motivation, and after picking it back up, I was so happy and energized. I reworked the art to make it look really great, I added more sounds, I hired a music artist, I started implementing online multiplayer, I set a personal roadmap for release on itch.io later this year.
And then this whole thing happened, and now my motivation is back to zero. It's just extra frustrating to me because I was so excited about finally getting back the motivation that I had previously lost.
So yea. Thanks for reading, I guess. <3
---
layout: blog
title: 😔 About Cross-Platform and Motivation
description: How moving from Java to C# taught me how horrible it is to create a cross-platform application with little to no knowledge or documentation
tags: [Programming]
discuss: https://twitter.com/Ellpeck/status/1147502654236573697
archived: true
---
As originally a java kid, I've never had much trouble with making any of my programs work on Windows, Linux and Mac. With Java, all you have to do is make a jar and then tell people to install Java on their machine. That's it. But oh boy, am I getting hit in the face hard now that I'm making a game with MonoGame and .NET Framework.
# The story, with all its horrible parts
Imagine you're me. Imagine you're just happily making a game and eventually you decide that you want to distribute it to testers.
## The documentation
So you check the documentation of the framework you're using: MonoGame, in my case. It advertises itself as being amazing at cross-platform, so I look around to find a simple tutorial on what to do to get my game working on Mac and Linux.
Mono is where it's at. Okay. An open source implementation of .NET (or something like that) that works on Linux and Mac. Great! Now I just need to find out how to easily package that with my game, so that people on those operating systems don't have to install it on their own.
## Mono Kickstart
So there's a project on GitHub with a MonoGame fork (meaning they should work together flawlessly!) called Mono Kickstart. It says that you can just add the files to your project, rename a couple of them, change one or two lines in a bash script and then your game will run smoothly on Linux and Mac without having to install Mono.
Yea, no. Some random issue about `System.Runtime.dll` not being found, no trace of the issue online, no tutorials anywhere, the Kickstart repository doesn't have an Issues tab for some reason. Dead end.
## mkbundle
Searching some more online, I found a tool contained in Mono called mkbundle, that, apparently, you can use to bundle standalone applications for Windows, Linux and Mac by just running a simple command. Seems easy enough.
Yea, no. Installing Mono on Windows is a huge pain. mkbundle's target downloading doesn't work on Windows, so I have to go into some obscure folders, download some obscure zips, rename them, move them around until it finally works.
Hah. For some reason, mkbundle now requires me to install Visual Studio. So I install their commandline build tools that, after asking in my Discord, are apparently what mkbundle wants to use for something.
So then it finally works, and I have a... yea, no. Apparently now, mkbundle can't find `System.Runtime.dll`, just like Mono Kickstart couldn't before. So I search around in some folders and notice that that dll is in a sub-folder instead of the main folder it tries to look for it in, so I tell mkbundle that it's in that subfolder through a long and annoying addition to the command, and now it finally works.
Yea, just kidding. Now it crashes because it can't find `stdint.h`. It's neither telling me where it's trying to look for it, nor why it even needs it, what it is or really anything of use at all. Dead end.
## .NET Core
After some more looking, I find that .NET Core is basically a better version of .NET Framework (it's actually a lot different, but that doesn't matter in the scope of this post) that actually runs on multiple platforms natively.
So I go through the process of upgrading my game project from .NET Framework to .NET Core, and everything finally works. I can really easily use my IDE (Rider) to publish packaged builds of the game for Windows, Linux and Mac that don't even need the user to have .NET Core installed.
Except that it doesn't run on my Laptop for some strange reason. It runs fine on my PC, and it runs fine on my boyfriend's computer and his Linux VM, but my Laptop has some obscure issue about MonoGame not being able to load the shaders it requires. There are also no traces of the issue online, and after some looking around, I realize that MonoGame's .NET Core implementation is, according to the repository, just a hack that also hasn't been updated in over a year. Dead end.
(Also, I had a weird issue where, with the newest version of MonoGame for .NET Core, my game would randomly stutter, and while the MonoGame Discord was unhelpful and one person was frankly unreasonably rude in my opinion, I found out that it isn't the fault of my code (after plenty of profiling) and that the issue is easily fixed by downgrading to an earlier version. Why? Who even knows at this point.)
## So I guess I'll just force the user to install Mono themselves then
I tested around a bit after going back to .NET Framework and it turns out that installing Mono on Linux isn't actually that bad, and after you installed it, you don't even have to run a special command to get the game to run. All you have to do is go into a terminal and run the game's normal exe, and apparently mono figures itself out without needing to specifically call it. So that's great!
Except that, on Mac, it seems to be a whole nother story altogether. The one person I asked to try it on Mac installed Mono, but then the system didn't seem to realize that it was actually installed, as the `mono` command didn't work, and just running the game's exe directly also didn't work.
## Other stuff I tried
Because it's so fun, here's a list of other stuff that I tried to use:
* Some obscure NuGet package that's supposed to make an easy `msbuild` command to bundle the game. Also didn't work, but at least I submitted an issue to the project's GitHub repository. We'll see what happens, maybe this will be the saving grace.
* Some obscure program from three years ago, with outdated packages and outdated dependencies, that was supposed to make it really easy to bundle projects for all operating systems. It had the same `System.Runtime.dll` issue, but because it was so outdated anyway, I didn't even bother investigating why.
## Conclusion?
Honestly, I don't know what to do at this point. After finding out how horrible it is to make anything work cross-platform, I'm genuinely considering just rewriting the entire game using libgdx (which is a game framework for Java), or something.
There's no real conclusion to this, other than that it took me a whole lot of my time and energy and that it made me buy and eat way too many tubs of Ben & Jerry's ice cream, which is way too expensive, by the way.
# Also, the Multiplayer debacle
As a little side note, I recently added Online Multiplayer to the game in question, using the C# networking library Lidgren.
Its UPnP implementation doesn't seem to be with my router (or my something else), so I have to manually forward ports as if it were 2003.
Also, one of the testers has a completely different issue about incomplete packets that I have no idea about, and frankly, my energy to work on anything is so low at this point that I might as well just not care about it.
# Conclusion
If you're thinking about becoming an indie developer, you'll have to deal with this sort of stuff. And honestly, it's going to bring you down. Quite a lot.
A couple weeks ago, I picked up this project again after taking a long break from it out of lack of motivation, and after picking it back up, I was so happy and energized. I reworked the art to make it look really great, I added more sounds, I hired a music artist, I started implementing online multiplayer, I set a personal roadmap for release on itch.io later this year.
And then this whole thing happened, and now my motivation is back to zero. It's just extra frustrating to me because I was so excited about finally getting back the motivation that I had previously lost.
So yea. Thanks for reading, I guess. <3

View file

@ -1,46 +1,54 @@
So I'm releasing a game soon. In December, to be exact. Here's some stuff I did and learned after telling myself that [Foe Frenzy](https://ellpeck.itch.io/FoeFrenzy) would be a [Small Project](https://ellpeck.de/#blog-small_projects) until it suddenly became the first game I'd ever release as a commercial product.
# In Discord
After deciding that I'd only be selling my game on itch.io, I quickly realized that that might not be the best of ideas in the long run, because few people actually know about the platform - despite it being pretty great.
So I decided to also sell the game on Discord - the Skype-Teamspeak-IRC-hybrid that recently also became a game store. As it turns out, this was *surprisingly* easy: All you have to do is pay $25, go through a really well organized checklist that is provided in Discord's usual jokey writing style and submit. Tada! Your game's on Discord now.
By the way, in case you actually want to see what Foe Frenzy's Discord store page currently looks like before release, you should [join my Discord](https://ellpeck.de/discord) and check out its `#ff-store` channel (on Desktop). It looks pretty snazzy!
## Oh, also:
As it turns out, Discord has a game SDK. And Achievement support. And Lobbies, and Multiplayer, and Invites.
And surprisingly, integrating all of that with Foe Frenzy wasn't necessarily the breeziest breeze ever, as I didn't really have any example implementations or anything of the sort at hand, but it worked out in the end!
Foe Frenzy will have full compatibility with Discord's Multiplayer features. What does this include, you ask?
* After hosting a Multiplayer game from Foe Frenzy, you can invite your friends to play with you through the simple click of a button: <blockquote class="twitter-tweet"><p lang="en" dir="ltr">You&#39;ll also be able to easily invite people from inside the game! <a href="https://t.co/f3dixbwnxD">pic.twitter.com/f3dixbwnxD</a></p>&mdash; Ell (@Ellpeck) <a href="https://twitter.com/Ellpeck/status/1172990814072557573?ref_src=twsrc%5Etfw">September 14, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
* When receiving an invite, you can simply click the Join button which will launch the game and instantly send you into your friend's online lobby: <blockquote class="twitter-tweet"><p lang="en" dir="ltr">SOMETHING IS COMING <a href="https://t.co/ZWVUuRDhD3">pic.twitter.com/ZWVUuRDhD3</a></p>&mdash; Ell (@Ellpeck) <a href="https://twitter.com/Ellpeck/status/1172954763803136001?ref_src=twsrc%5Etfw">September 14, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
* From there, you can play online without needing to expose your IP, fiddle around with your router settings or really do anything else.
* And have I mentioned... you'll be able to use these features even when buying the game from itch.io, you'll be able to combine Discord and IP-based multiplayer, and you'll even be able to combine online multiplayer and local multiplayer! *So many possibilities!*
So yea. That's pretty exciting.
# Music and Trailer
So for a rather long time now, I've been watching a YouTuber called [ThinMatrix](https://youtube.com/ThinMatrix) develop his own game, and simultaneously listening to the amazing music for said game, created by the lovely [Jamal Green](https://twitter.com/JamalGreenMusic).
So after some contemplating (and a lot of anxiety), I decided to send him an email, and quite quickly, he replied and said that, yes indeed, he would love to create the soundtrack for Foe Frenzy. So that's what he's doing now, and it's super awesome.
In case you haven't seen it yet, here's the announcement trailer for the game which features the awesome trailer theme that he created:
<iframe width="560" height="315" src="https://www.youtube.com/embed/Z7ZeuVBNuf4" frameborder="0" allow="accelerometer; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
So the moral of that is basically: If you like an artist and want to cooperate with them, don't be scared to message them - the worst that can happen is they say no, and the best that can happen is that they create an awesome soundtrack for you!
# The whole platform issue
If you read all of my blog posts, you'll know about the previous one I made about me trying to figure out how to get a MonoGame project to work well cross-platform without having to upgrade to the not-yet-well-supported .NET Core.
As it turns out, all I had to do was actually use versions of .NET Framework and netstandard (which is another build target that can be quite confusing to people that aren't used to the whole .NET environment and the weird state it's currently in) that actually, you know, *are compatible with each other*. So after sorting that out, I made [a little tool](https://github.com/Ellpeck/MonoKickstartAutoBundle) that allows you to easily pack any .NET Framework application with Mono without having to even do so much as break a sweat. So that was quite handy.
# Oh God, what if no one buys my game
Obviously, I have a lot of anxiety, as my Twitter feed, the `#feels` channel on my Discord and my therapist make pretty clear.
So a feeling I have almost *constantly* is the fear that my game won't go well. That no one will care, that no one will buy it, that no one will enjoy it, that it'll be rated horribly, that YouTubers will make fun of it, that... honestly, so many more stupid fears. And obviously, the release hasn't happened yet, and so those fears are still there. But the best advice I can give is to just power through those fears if you also have them, because, as with the e-mail, the worst thing that can happen is that they come true - but the best thing that can happen is that they won't. So, really, the only way it can go is up.
But also, in case you *do* buy the game once it comes out, and in case you also like it, please tell me. It would mean the world to me. Thanks so much.
Also, thanks for reading. <3
---
layout: blog
title: ⚔️ Big Projects
description: "How a once small project I even created a post about turned into the first game I'm selling: Foe Frenzy"
tags: [Programming]
discuss: https://twitter.com/Ellpeck/status/1173247686654517249
---
So I'm releasing a game soon. In December, to be exact. Here's some stuff I did and learned after telling myself that [Foe Frenzy](https://ellpeck.itch.io/FoeFrenzy) would be a [Small Project](https://ellpeck.de/#blog-small_projects) until it suddenly became the first game I'd ever release as a commercial product.
# In Discord
After deciding that I'd only be selling my game on itch.io, I quickly realized that that might not be the best of ideas in the long run, because few people actually know about the platform - despite it being pretty great.
So I decided to also sell the game on Discord - the Skype-Teamspeak-IRC-hybrid that recently also became a game store. As it turns out, this was *surprisingly* easy: All you have to do is pay $25, go through a really well organized checklist that is provided in Discord's usual jokey writing style and submit. Tada! Your game's on Discord now.
By the way, in case you actually want to see what Foe Frenzy's Discord store page currently looks like before release, you should [join my Discord](https://link.ellpeck.de/discordweb) and check out its `#ff-store` channel (on Desktop). It looks pretty snazzy!
## Oh, also:
As it turns out, Discord has a game SDK. And Achievement support. And Lobbies, and Multiplayer, and Invites.
And surprisingly, integrating all of that with Foe Frenzy wasn't necessarily the breeziest breeze ever, as I didn't really have any example implementations or anything of the sort at hand, but it worked out in the end!
Foe Frenzy will have full compatibility with Discord's Multiplayer features. What does this include, you ask?
* After hosting a Multiplayer game from Foe Frenzy, you can invite your friends to play with you through the simple click of a button: <blockquote class="twitter-tweet"><p lang="en" dir="ltr">You&#39;ll also be able to easily invite people from inside the game! <a href="https://t.co/f3dixbwnxD">pic.twitter.com/f3dixbwnxD</a></p>&mdash; Ell (@Ellpeck) <a href="https://twitter.com/Ellpeck/status/1172990814072557573?ref_src=twsrc%5Etfw">September 14, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
* When receiving an invite, you can simply click the Join button which will launch the game and instantly send you into your friend's online lobby: <blockquote class="twitter-tweet"><p lang="en" dir="ltr">SOMETHING IS COMING <a href="https://t.co/ZWVUuRDhD3">pic.twitter.com/ZWVUuRDhD3</a></p>&mdash; Ell (@Ellpeck) <a href="https://twitter.com/Ellpeck/status/1172954763803136001?ref_src=twsrc%5Etfw">September 14, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
* From there, you can play online without needing to expose your IP, fiddle around with your router settings or really do anything else.
* And have I mentioned... you'll be able to use these features even when buying the game from itch.io, you'll be able to combine Discord and IP-based multiplayer, and you'll even be able to combine online multiplayer and local multiplayer! *So many possibilities!*
So yea. That's pretty exciting.
# Music and Trailer
So for a rather long time now, I've been watching a YouTuber called [ThinMatrix](https://youtube.com/ThinMatrix) develop his own game, and simultaneously listening to the amazing music for said game, created by the lovely [Jamal Green](https://twitter.com/JamalGreenMusic).
So after some contemplating (and a lot of anxiety), I decided to send him an email, and quite quickly, he replied and said that, yes indeed, he would love to create the soundtrack for Foe Frenzy. So that's what he's doing now, and it's super awesome.
In case you haven't seen it yet, here's the announcement trailer for the game which features the awesome trailer theme that he created:
<iframe width="560" height="315" src="https://www.youtube.com/embed/Z7ZeuVBNuf4" frameborder="0" allow="accelerometer; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
So the moral of that is basically: If you like an artist and want to cooperate with them, don't be scared to message them - the worst that can happen is they say no, and the best that can happen is that they create an awesome soundtrack for you!
# The whole platform issue
If you read all of my blog posts, you'll know about the previous one I made about me trying to figure out how to get a MonoGame project to work well cross-platform without having to upgrade to the not-yet-well-supported .NET Core.
As it turns out, all I had to do was actually use versions of .NET Framework and netstandard (which is another build target that can be quite confusing to people that aren't used to the whole .NET environment and the weird state it's currently in) that actually, you know, *are compatible with each other*. So after sorting that out, I made [a little tool](https://github.com/Ellpeck/MonoKickstartAutoBundle) that allows you to easily pack any .NET Framework application with Mono without having to even do so much as break a sweat. So that was quite handy.
# Oh God, what if no one buys my game
Obviously, I have a lot of anxiety, as my Twitter feed, the `#feels` channel on my Discord and my therapist make pretty clear.
So a feeling I have almost *constantly* is the fear that my game won't go well. That no one will care, that no one will buy it, that no one will enjoy it, that it'll be rated horribly, that YouTubers will make fun of it, that... honestly, so many more stupid fears. And obviously, the release hasn't happened yet, and so those fears are still there. But the best advice I can give is to just power through those fears if you also have them, because, as with the e-mail, the worst thing that can happen is that they come true - but the best thing that can happen is that they won't. So, really, the only way it can go is up.
But also, in case you *do* buy the game once it comes out, and in case you also like it, please tell me. It would mean the world to me. Thanks so much.
Also, thanks for reading. <3

View file

@ -1,97 +1,104 @@
So you want to learn to code. In my opinion, a good language for beginners is Java, because it's a rather easy to understand language, and it's cross-platform, meaning it can run on any operating system without the need for the programmer (you!) to change anything based on the platform. Also, Java is object-oriented (more on that later), meaning it's pretty good for writing any sort of simple or complex program.[^1]
So let's start!
# Setting it up
To actually be able to compile[^2] any Java code, you first need to download the [Java Development Kit](https://jdk.java.net/), or JDK for short. If you're a Linux or Mac user, you can just follow [their installation tutorial](https://openjdk.java.net/install/), and if you're on Windows, you can follow [this tutorial from Stack Overflow](https://stackoverflow.com/a/52531093). To check if the installation worked, you can just type `java -version` into a command prompt (which you can open on Windows by just typing `cmd` into your search).
Some people might disagree with me on this, but I recommend using an Integrated Development Environment (IDE) for development *especially* when you're just starting out. An IDE is a program that helps you with coding by suggesting changes, notifying you of errors in your code and also allows you to easily run and debug your application. My personal suggestion would be to install [IntelliJ IDEA](https://www.jetbrains.com/idea/), but you could also use other IDEs like [Eclipse](https://www.eclipse.org/downloads/).
The next thing you'll have to do is create a new project inside your IDE. For IntelliJ, all you have to do is click `New Project`, select `Java`, click `Next` twice and then type your project's name. I'm going to go with `JavaTutorial` for mine.
# Hello World
Once that's done, we can finally start programming. At first, you'll find that a lot of the stuff we're doing isn't explained directly, but I'll come back to some of the things I'm showing you now at a later point. Things like classes, methods and parameters probably won't make sense to you yet, but using them is required for even the most basic of running programs.
The first thing we want to do is create a simple program that outputs something back to the user inside of the program's console. To start, simply right click your `src` folder and select `New` and `Java Class` and give it a name like `Main`. I'll show you the code for it first and then give you a quick rundown of the important parts you need to know about it for now.
```java
public class Main {
public static void main(String[] args) {
System.out.println("Hello Tutorial World!");
}
}
```
That's it! If you now run your program (for IntelliJ, there should be a small green arrow to the left that you can click on), it'll print out the message to the console and then stop the program.
A lot of the code you see above doesn't *really* matter to you right now. The important stuff is this:
- Each instruction, each thing that the program should do, ends with a semicolon `;` and usually a new line, though that is optional.
- Any instruction you write between the inner curly braces `{}` will be executed in order of how it's written.
- `System.out.println("<enter anything here>")` is an instruction that causes a line of text to be written to the console.
As a little exercise, you can make your program output another line of text:
```java
public class Main {
public static void main(String[] args) {
System.out.println("Hello Tutorial World!");
System.out.println("This is another line of text!");
}
}
```
# Variables
Now the first thing you'll probably want to do is learn what variables are and how to use them. A variable, simply put, is any kind of value (a number, a string of text etc.) that can be changed and that has a name. In Java, each variable also has to have a fixed type: While a variable's *value* can be changed (say, from `some text` to `some other text`), its *type* cannot be changed (so a variable representing text cannot be changed into a variable representing a number). Variables are created as follows:
```java
public class Main {
public static void main(String[] args) {
System.out.println("Hello Tutorial World!");
System.out.println("This is another line of text!");
String someText;
int someNumber;
someText = "Some Text";
someNumber = 10;
}
}
```
As you can see from line 6 and 7, to declare a variable, you need to put the variable's type first (`String` meaning text, `int` meaning an integer; a number without a decimal point), followed by the variable's name, which you can choose yourself.
For now, these variables don't have a value assigned to them yet. That's done in line 9 and 10. To assign a value to a variable, you first put the variable's name, followed by an equals sign `=`, followed by your desired value. For numbers, you can simply put them down, but text has to be wrapped in quotes `""` (which is also why the text in lines 3 and 4 is wrapped in quotes).
To actually do something with the variables, let's have them be printed out to the console. *I've omitted the rest of the code here because it's getting quite long, but I'm just adding the following lines inside the inner braces `{}` below the already written code.*
```java
System.out.println(someText);
System.out.println(someNumber);
```
As you can see, to print a variable's value to the console, all you have to do is put its name where you would usually put a piece of text directly.
# Variable Manipulation
Obviously, this isn't really that exciting yet: Our program isn't really doing anything so far. Let's manipulate our variables. I've added the following code:
```java
someNumber = someNumber + 5;
System.out.println(someNumber);
```
As you can see, you can use a plus sign `+` (as well as other mathematical operators) to do math with numbers. The first statement changes `someNumber`'s value to a new value: `someNumber + 5`. Running the program will show you that the result is pretty obvious: 15 is printed, because 10 + 5 = 15.
The same kind of manipulation can be done with text, which is especially useful for printing variables to the console. The plus sign `+` chains two strings of text together.
```java
String chainedText = "The resulting value is " + someNumber;
System.out.println(chainedText);
```
Here, a new variable is created (`String chainedText`) and its value is set (`=`) to the shown string plus the value of the `someNumber` variable. When added to the rest of the code, running this code should display `The resulting value is 15`.
# Conclusion
Now obviously, this is just the beginning of what you can learn and do with Java. I feel like this is a good point to finish the first part of this tutorial. I know it's probably not super interesting yet, but bear with me for a little while and you'll pretty quickly be able to do some pretty cool stuff.
Before you click away, I'd like to ask you some questions that you can answer by clicking on the discussion link below this post:
- Could you follow this tutorial?
- What do you think about the pacing? Is it too fast or too slow, are there too many examples or too few?
- Do you want me to actually continue this tutorial series?
Anyway, thanks a lot for reading and happy coding! <3
***
[^1]: Also, it's what Minecraft mods are written in, which is probably the reason most of you are here.
[^2]: Compiling is what occurs when code written by you is converted into something that the computer can actually understand. More on that later, probably.
---
layout: blog
title: "☕ Java Tutorial, Part 1: Hello World"
description: The first part of my post series for programming beginners where I explain how to write code in Java.
tags: [Programming]
discuss: https://twitter.com/Ellpeck/status/1182080078827737088
archived: true
---
So you want to learn to code. In my opinion, a good language for beginners is Java, because it's a rather easy to understand language, and it's cross-platform, meaning it can run on any operating system without the need for the programmer (you!) to change anything based on the platform. Also, Java is object-oriented (more on that later), meaning it's pretty good for writing any sort of simple or complex program.[^1]
So let's start!
# Setting it up
To actually be able to compile[^2] any Java code, you first need to download the [Java Development Kit](https://jdk.java.net/), or JDK for short. If you're a Linux or Mac user, you can just follow [their installation tutorial](https://openjdk.java.net/install/), and if you're on Windows, you can follow [this tutorial from Stack Overflow](https://stackoverflow.com/a/52531093). To check if the installation worked, you can just type `java -version` into a command prompt (which you can open on Windows by just typing `cmd` into your search).
Some people might disagree with me on this, but I recommend using an Integrated Development Environment (IDE) for development *especially* when you're just starting out. An IDE is a program that helps you with coding by suggesting changes, notifying you of errors in your code and also allows you to easily run and debug your application. My personal suggestion would be to install [IntelliJ IDEA](https://www.jetbrains.com/idea/), but you could also use other IDEs like [Eclipse](https://www.eclipse.org/downloads/).
The next thing you'll have to do is create a new project inside your IDE. For IntelliJ, all you have to do is click `New Project`, select `Java`, click `Next` twice and then type your project's name. I'm going to go with `JavaTutorial` for mine.
# Hello World
Once that's done, we can finally start programming. At first, you'll find that a lot of the stuff we're doing isn't explained directly, but I'll come back to some of the things I'm showing you now at a later point. Things like classes, methods and parameters probably won't make sense to you yet, but using them is required for even the most basic of running programs.
The first thing we want to do is create a simple program that outputs something back to the user inside of the program's console. To start, simply right click your `src` folder and select `New` and `Java Class` and give it a name like `Main`. I'll show you the code for it first and then give you a quick rundown of the important parts you need to know about it for now.
```java
public class Main {
public static void main(String[] args) {
System.out.println("Hello Tutorial World!");
}
}
```
That's it! If you now run your program (for IntelliJ, there should be a small green arrow to the left that you can click on), it'll print out the message to the console and then stop the program.
A lot of the code you see above doesn't *really* matter to you right now. The important stuff is this:
- Each instruction, each thing that the program should do, ends with a semicolon `;` and usually a new line, though that is optional.
- Any instruction you write between the inner curly braces `{}` will be executed in order of how it's written.
- `System.out.println("<enter anything here>")` is an instruction that causes a line of text to be written to the console.
As a little exercise, you can make your program output another line of text:
```java
public class Main {
public static void main(String[] args) {
System.out.println("Hello Tutorial World!");
System.out.println("This is another line of text!");
}
}
```
# Variables
Now the first thing you'll probably want to do is learn what variables are and how to use them. A variable, simply put, is any kind of value (a number, a string of text etc.) that can be changed and that has a name. In Java, each variable also has to have a fixed type: While a variable's *value* can be changed (say, from `some text` to `some other text`), its *type* cannot be changed (so a variable representing text cannot be changed into a variable representing a number). Variables are created as follows:
```java
public class Main {
public static void main(String[] args) {
System.out.println("Hello Tutorial World!");
System.out.println("This is another line of text!");
String someText;
int someNumber;
someText = "Some Text";
someNumber = 10;
}
}
```
As you can see from line 6 and 7, to declare a variable, you need to put the variable's type first (`String` meaning text, `int` meaning an integer; a number without a decimal point), followed by the variable's name, which you can choose yourself.
For now, these variables don't have a value assigned to them yet. That's done in line 9 and 10. To assign a value to a variable, you first put the variable's name, followed by an equals sign `=`, followed by your desired value. For numbers, you can simply put them down, but text has to be wrapped in quotes `""` (which is also why the text in lines 3 and 4 is wrapped in quotes).
To actually do something with the variables, let's have them be printed out to the console. *I've omitted the rest of the code here because it's getting quite long, but I'm just adding the following lines inside the inner braces `{}` below the already written code.*
```java
System.out.println(someText);
System.out.println(someNumber);
```
As you can see, to print a variable's value to the console, all you have to do is put its name where you would usually put a piece of text directly.
# Variable Manipulation
Obviously, this isn't really that exciting yet: Our program isn't really doing anything so far. Let's manipulate our variables. I've added the following code:
```java
someNumber = someNumber + 5;
System.out.println(someNumber);
```
As you can see, you can use a plus sign `+` (as well as other mathematical operators) to do math with numbers. The first statement changes `someNumber`'s value to a new value: `someNumber + 5`. Running the program will show you that the result is pretty obvious: 15 is printed, because 10 + 5 = 15.
The same kind of manipulation can be done with text, which is especially useful for printing variables to the console. The plus sign `+` chains two strings of text together.
```java
String chainedText = "The resulting value is " + someNumber;
System.out.println(chainedText);
```
Here, a new variable is created (`String chainedText`) and its value is set (`=`) to the shown string plus the value of the `someNumber` variable. When added to the rest of the code, running this code should display `The resulting value is 15`.
# Conclusion
Now obviously, this is just the beginning of what you can learn and do with Java. I feel like this is a good point to finish the first part of this tutorial. I know it's probably not super interesting yet, but bear with me for a little while and you'll pretty quickly be able to do some pretty cool stuff.
Before you click away, I'd like to ask you some questions that you can answer by clicking on the discussion link below this post:
- Could you follow this tutorial?
- What do you think about the pacing? Is it too fast or too slow, are there too many examples or too few?
- Do you want me to actually continue this tutorial series?
Anyway, thanks a lot for reading and happy coding! <3
[^1]: Also, it's what Minecraft mods are written in, which is probably the reason most of you are here.
[^2]: Compiling is what occurs when code written by you is converted into something that the computer can actually understand. More on that later, probably.

View file

@ -1,163 +1,170 @@
If you're reading this, then I assume you have already read the first part of this tutorial series, in which we covered setting up a program, making it print "Hello World" to the console, as well as how to declare and set the values of variables.
Today, we'll be talking about conditions and loops.[^1] As with the last tutorial, I'll be writing most of the code snippets first and then explaining what exactly they mean and do right after.
# The `if` condition
Let's say you want to create a program that checks if a given number `i` is greater than or equal to 27. Well, you do it like this:
```java
public class Main {
public static void main(String[] args) {
// This is where the code from the first tutorial would be
// if I hadn't deleted it for visual clarity
// Also, side note: You can use "//" to create comments:
// lines that aren't interpreted as code.
int i = 15;
if (i >= 27) {
System.out.println("i is greater than or equal to 27!");
}
}
}
```
So what we see here is called the `if` condition. It's structured as follows: You write the word `if`, followed by *the condition*, which is wrapped in parentheses `()`. You can then open curly braces `{}`, and any instructions that are written between them will only be executed if your supplied condition holds true.
The condition can be any statement that can either be `true` (correct) or `false` (incorrect). In this case, we're comparing two numbers with each other; there are several other ways to compare two numbers: `>`, `<`, `>=`, `<=`, `!=` and `==`, where the last two mean "not equal" and "exactly equal".[^2]
An important thing to note at this point is that this behavior is *different* with `String` variables.[^3] Comparing if two strings are equal by using `==` will not result in the behavior you might expect. Instead, you should compare two strings using `equals()` as follows:
```java
String s1 = "This is some text";
String s2 = "This is also some text";
if (s1.equals(s2)) {
System.out.println("s1 and s2 are equal!");
}
```
## The `boolean` type
A condition like this (which can either be `true` or `false`) can also be stored in a `boolean` variable[^4] whose state can then be checked using a similar `if` statement:
```java
int i = 15;
boolean i27 = i >= 27;
if (i27) {
System.out.println("i is greater than or equal to 27!");
}
```
The above code has the same effect as the code we looked at before. The cool thing about this method is that you can easily check if a condition *doesn't* hold true by simply adding an exclamation point `!` in front of any boolean variable:
```java
if (!i27) {
System.out.println("i is NOT greater than or equal to 27!");
}
// As you can see, this is especially useful when comparing strings
String s1 = "This is some text";
String s2 = "This is also some text";
if (!s1.equals(s2)) {
System.out.println("s1 and s2 are NOT equal!");
}
```
Additionally, two boolean variables (or simply two conditions) can be chained together in two ways:
```java
int i = 15;
if (i == 12 || i == 13) {
System.out.println("i is either 12 or 13!");
}
if (i >= 10 && i < 20) {
System.out.println("i is somewhere between 10 and 20");
}
```
As you can see in lines 2 to 4, two pipes `||` represent the `OR` operator: A combined condition using it holds true if the left side is true, or the right side is true, or both sides are true.
As you can see in lines 5 to 7, two ampersands `&&` represent the `AND` operator: A combined condition using it holds true if *both* sides are also true.
You can also wrap your checks in parentheses `()` to create a more complex condition, similarly to how you would use them in a mathematical expression:
```java
int j = 10;
int k = 15;
boolean combination = (j == 12 && k == 13) || (j == 10 && k == 25);
System.out.println(combination);
```
The above code will cause `true` to be printed if `j` equals 12 and `k` equals 13 *or* if `j` equals 10 and `k` equals 25. Otherwise, `false` will be printed.
## `else`
If you want something *else* to happen if a certain condition doesn't hold true, then you can do something like the following:
```java
int i = 15;
if (i >= 27) {
System.out.println("i is greater than or equal to 27!");
} else {
System.out.println("i is less than 27 :(");
}
```
As you can see, writing `else` after any `if` statement causes the code contained in the following curly braces `{}` to only be executed if the `if` statement's condition *isn't* true.
Optionally, you can also combine the `else` with another `if` statement to check for a different condition. You can do this as many times as you want:
```java
int i = 15;
if (i >= 27) {
System.out.println("i is greater than or equal to 27!");
} else if (i <= 10) {
System.out.println("i is less than 27, but also less than or equal to 10");
} else {
System.out.println("i is somewhere between 11 and 26");
}
```
# The `for` loop
The `if` condition is already pretty powerful, because you can execute different code based on different situations. Another useful thing is the `for` loop. How it's written and what it does can be a bit complicated to understand at first, but I'll try to break the following example down for you:
```java
for (int index = 0; index < 3; index = index + 1) {
System.out.println("The current index is " + index);
}
```
This specific `for` loop causes the following to be printed to the console:
```
The current index is 0
The current index is 1
The current index is 2
```
As you might be able to tell from that, a `for` loop causes any number of instructions in it to be executed a certain amount of times.
Its structure is pretty similar to the `if` statement's: First, you write the word `for`, followed by some loop instructions inside parentheses `()`, and then you open curly braces `{}` which contain the instructions that should be executed multiple times. The loop instructions contain three parts, which are separated by semicolons `;`:
- `int index = 0;` is the declaration of the *loop variable*: This is the instruction that will be executed *before* the loop starts running.
- `index < 3;` is the *loop's condition*: Before every time the loop's content is run, a check is done to make sure that this condition is still true. If it's not true anymore, the loop stops running.
- `index = index + 1` is the instruction that is executed *after* every time the loop's content has finished running.
So in the case of our loop, the following things happen:
- The `index` variable is declared and set to 0.
- `index < 3` is checked, which is obviously true.
- The loop's content is run, which causes the print to the console.
- `index = index + 1` is executed, which sets `index` to 1.
- `index < 3` is checked, which is still true.
- The loop's content is run again, and so on.
## `break`
If you want to exit a `for` loop before its condition turns to `false`, you can use the `break;` statement as follows:
```java
for (int index = 0; index < 10000; index = index + 1) {
System.out.println("The current index is " + index);
if (index >= 2) {
break;
}
}
```
This loop has the same effect as the one we looked at before. Despite the loop condition specifying that it should repeat 10000 times, it is only repeated three times, because the condition on line 4 is evaluated every time and the loop is forced to stop running once it is true.
# Conclusion
In this tutorial, you (hopefully) learned what the `if` and `for` statements are and how to use them. By now, you should be able to write some more complicated code. As a little exercise, you can try to solve the following problem using the things you learned in the last two tutorials:
> Given a variable `i` and another variable `exp`, calculate if the result of `i`<sup>`exp`</sup> (`i` to the power of `exp`) is less than or equal to the value of another variable `j` (and print the result to the console accordingly).
If you're stuck, you can check out my solution [here](https://gist.github.com/Ellpeck/999bafe352f84d5e1f09750e026d5bbf).
Thanks for reading this tutorial and I hope it helped you out! If you have any feedback or questions about it, you can click the discussion link below and ask me on Twitter, or join my Discord server using the widget on the main page. Happy coding!
***
[^1]: I'm covering this topic *before* I cover what exactly classes and methods are. I think that conditions and loops take importance here, because they're used broadly in every language as well as most programs, whereas object orientation is a feature specific to some languages, as well as specific to more "complex" programs. It's also somewhat complicated, and I want to explain it right, because when I learned Java, I didn't even remotely understand it correctly at first.
[^2]: Note that, when *comparing* two numbers, two equals signs `==` are used. This is different from *assigning a value* to a variable, which only uses one equals sign `=`.
[^3]: Why exactly this is the case will be discussed later when we get into object orientation. The different behavior mentioned here is the case for any variable types which aren't so-called *native types*. Any variable whose type starts with an uppercase letter is not a native type, because it derives from a *class*.
[^4]: Named after [George Boole](https://en.wikipedia.org/wiki/George_Boole), a mathematician.
---
layout: blog
title: "☕ Java Tutorial, Part 2: Intro to Conditions and Loops"
description: The second part of my post series for programming beginners. This one is all about conditions and loops.
tags: [Programming]
discuss: https://twitter.com/Ellpeck/status/1182354544707198976
archived: true
---
If you're reading this, then I assume you have already read the first part of this tutorial series, in which we covered setting up a program, making it print "Hello World" to the console, as well as how to declare and set the values of variables.
Today, we'll be talking about conditions and loops.[^1] As with the last tutorial, I'll be writing most of the code snippets first and then explaining what exactly they mean and do right after.
# The `if` condition
Let's say you want to create a program that checks if a given number `i` is greater than or equal to 27. Well, you do it like this:
```java
public class Main {
public static void main(String[] args) {
// This is where the code from the first tutorial would be
// if I hadn't deleted it for visual clarity
// Also, side note: You can use "//" to create comments:
// lines that aren't interpreted as code.
int i = 15;
if (i >= 27) {
System.out.println("i is greater than or equal to 27!");
}
}
}
```
So what we see here is called the `if` condition. It's structured as follows: You write the word `if`, followed by *the condition*, which is wrapped in parentheses `()`. You can then open curly braces `{}`, and any instructions that are written between them will only be executed if your supplied condition holds true.
The condition can be any statement that can either be `true` (correct) or `false` (incorrect). In this case, we're comparing two numbers with each other; there are several other ways to compare two numbers: `>`, `<`, `>=`, `<=`, `!=` and `==`, where the last two mean "not equal" and "exactly equal".[^2]
An important thing to note at this point is that this behavior is *different* with `String` variables.[^3] Comparing if two strings are equal by using `==` will not result in the behavior you might expect. Instead, you should compare two strings using `equals()` as follows:
```java
String s1 = "This is some text";
String s2 = "This is also some text";
if (s1.equals(s2)) {
System.out.println("s1 and s2 are equal!");
}
```
## The `boolean` type
A condition like this (which can either be `true` or `false`) can also be stored in a `boolean` variable[^4] whose state can then be checked using a similar `if` statement:
```java
int i = 15;
boolean i27 = i >= 27;
if (i27) {
System.out.println("i is greater than or equal to 27!");
}
```
The above code has the same effect as the code we looked at before. The cool thing about this method is that you can easily check if a condition *doesn't* hold true by simply adding an exclamation point `!` in front of any boolean variable:
```java
if (!i27) {
System.out.println("i is NOT greater than or equal to 27!");
}
// As you can see, this is especially useful when comparing strings
String s1 = "This is some text";
String s2 = "This is also some text";
if (!s1.equals(s2)) {
System.out.println("s1 and s2 are NOT equal!");
}
```
Additionally, two boolean variables (or simply two conditions) can be chained together in two ways:
```java
int i = 15;
if (i == 12 || i == 13) {
System.out.println("i is either 12 or 13!");
}
if (i >= 10 && i < 20) {
System.out.println("i is somewhere between 10 and 20");
}
```
As you can see in lines 2 to 4, two pipes `||` represent the `OR` operator: A combined condition using it holds true if the left side is true, or the right side is true, or both sides are true.
As you can see in lines 5 to 7, two ampersands `&&` represent the `AND` operator: A combined condition using it holds true if *both* sides are also true.
You can also wrap your checks in parentheses `()` to create a more complex condition, similarly to how you would use them in a mathematical expression:
```java
int j = 10;
int k = 15;
boolean combination = (j == 12 && k == 13) || (j == 10 && k == 25);
System.out.println(combination);
```
The above code will cause `true` to be printed if `j` equals 12 and `k` equals 13 *or* if `j` equals 10 and `k` equals 25. Otherwise, `false` will be printed.
## `else`
If you want something *else* to happen if a certain condition doesn't hold true, then you can do something like the following:
```java
int i = 15;
if (i >= 27) {
System.out.println("i is greater than or equal to 27!");
} else {
System.out.println("i is less than 27 :(");
}
```
As you can see, writing `else` after any `if` statement causes the code contained in the following curly braces `{}` to only be executed if the `if` statement's condition *isn't* true.
Optionally, you can also combine the `else` with another `if` statement to check for a different condition. You can do this as many times as you want:
```java
int i = 15;
if (i >= 27) {
System.out.println("i is greater than or equal to 27!");
} else if (i <= 10) {
System.out.println("i is less than 27, but also less than or equal to 10");
} else {
System.out.println("i is somewhere between 11 and 26");
}
```
# The `for` loop
The `if` condition is already pretty powerful, because you can execute different code based on different situations. Another useful thing is the `for` loop. How it's written and what it does can be a bit complicated to understand at first, but I'll try to break the following example down for you:
```java
for (int index = 0; index < 3; index = index + 1) {
System.out.println("The current index is " + index);
}
```
This specific `for` loop causes the following to be printed to the console:
```
The current index is 0
The current index is 1
The current index is 2
```
As you might be able to tell from that, a `for` loop causes any number of instructions in it to be executed a certain amount of times.
Its structure is pretty similar to the `if` statement's: First, you write the word `for`, followed by some loop instructions inside parentheses `()`, and then you open curly braces `{}` which contain the instructions that should be executed multiple times. The loop instructions contain three parts, which are separated by semicolons `;`:
- `int index = 0;` is the declaration of the *loop variable*: This is the instruction that will be executed *before* the loop starts running.
- `index < 3;` is the *loop's condition*: Before every time the loop's content is run, a check is done to make sure that this condition is still true. If it's not true anymore, the loop stops running.
- `index = index + 1` is the instruction that is executed *after* every time the loop's content has finished running.
So in the case of our loop, the following things happen:
- The `index` variable is declared and set to 0.
- `index < 3` is checked, which is obviously true.
- The loop's content is run, which causes the print to the console.
- `index = index + 1` is executed, which sets `index` to 1.
- `index < 3` is checked, which is still true.
- The loop's content is run again, and so on.
## `break`
If you want to exit a `for` loop before its condition turns to `false`, you can use the `break;` statement as follows:
```java
for (int index = 0; index < 10000; index = index + 1) {
System.out.println("The current index is " + index);
if (index >= 2) {
break;
}
}
```
This loop has the same effect as the one we looked at before. Despite the loop condition specifying that it should repeat 10000 times, it is only repeated three times, because the condition on line 4 is evaluated every time and the loop is forced to stop running once it is true.
# Conclusion
In this tutorial, you (hopefully) learned what the `if` and `for` statements are and how to use them. By now, you should be able to write some more complicated code. As a little exercise, you can try to solve the following problem using the things you learned in the last two tutorials:
> Given a variable `i` and another variable `exp`, calculate if the result of `i`<sup>`exp`</sup> (`i` to the power of `exp`) is less than or equal to the value of another variable `j` (and print the result to the console accordingly).
If you're stuck, you can check out my solution [here](https://gist.github.com/Ellpeck/999bafe352f84d5e1f09750e026d5bbf).
Thanks for reading this tutorial and I hope it helped you out! If you have any feedback or questions about it, you can click the discussion link below and ask me on Twitter, or join my Discord server using the widget on the main page. Happy coding!
[^1]: I'm covering this topic *before* I cover what exactly classes and methods are. I think that conditions and loops take importance here, because they're used broadly in every language as well as most programs, whereas object orientation is a feature specific to some languages, as well as specific to more "complex" programs. It's also somewhat complicated, and I want to explain it right, because when I learned Java, I didn't even remotely understand it correctly at first.
[^2]: Note that, when *comparing* two numbers, two equals signs `==` are used. This is different from *assigning a value* to a variable, which only uses one equals sign `=`.
[^3]: Why exactly this is the case will be discussed later when we get into object orientation. The different behavior mentioned here is the case for any variable types which aren't so-called *native types*. Any variable whose type starts with an uppercase letter is not a native type, because it derives from a *class*.
[^4]: Named after [George Boole](https://en.wikipedia.org/wiki/George_Boole), a mathematician.

View file

@ -1,175 +1,184 @@
I've been thinking about how to structure this tutorial a lot, and I decided to teach you all about methods *before* I get into object orientation, so you'll have to wait a little while longer before we get into the real nitty-gritty. To understand this one, you should've already read and understood tutorials 1 and 2. If you haven't, you can get to them by clicking the "Previous Post" button in the navigation bar.
So, here goes!
# Methods
Methods are a way, in programming, to move certain code into a different location where it's more organized and, more importantly, where it can be called multiple times from multiple locations, possibly with different data. You'll see what exactly that means in a minute.
Let's create a simple method in our main class:
```java
public class Main {
public static void main(String[] args) {
// I've omitted the code from the previous tutorials for readability
}
public static void printInfo() {
System.out.println("This is some important information!");
}
}
```
Let's take a look at the `printInfo` method I created. In this tutorial, we'll only be talking about `public` and `static` methods, so you'll just have to take that part for granted for now. Following that, you write `void` and then the name of your method (which can be anything you want), followed by parentheses `()` and then braces `{}`. Similarly to the `if` statement and the `for` loop, the method's content goes between those two curly braces, as I have done in this example with the print statement.
As you can see, all a method is is pretty much a collection of code. At this point, you might've already noticed that that is exactly what the `main` structure we've previously been writing all of our code into is: `main` is just another method.
To *call* a method, that is to have the code inside of it be executed, all you have to do is write the following:
```java
public class Main {
public static void main(String[] args) {
// I've omitted the code from the previous tutorials for readability
printInfo();
}
public static void printInfo() {
System.out.println("This is some important information!");
}
}
```
There you go.
## Variables
It should be noted at this point that variables which are declared *inside* of a method, like the ones we've been using in the first two tutorials, are known as `local variables`. What this means is that they only exist *inside* of the method they are declared in. Check out this example:
```java
// This method declares a variable i that is set to 0
public static void methodOne() {
int i = 0;
}
// This method declares a *different* variable i that is set to 1
public static void methodTwo() {
int i = 1;
}
// This method will cause an error if you paste it into your IDE:
// You cannot declare two variables with the same name in one method.
public static void erroringMethod() {
int i = 0;
int i = 1;
}
```
This same behavior also counts for `if`, `for` and any other curly braces `{}` that you see: They close off any variables which are created inside of them and make them available to only that location.
# Parameters
Now you might be wondering what the point of methods is if all they do is execute a predefined list of code. Well... you can actually make a method *accept* a set of data that they can use to do their processing. The data given to a method is called *parameters*, and these parameters are simply variables that you declare between the method's parentheses `()` right after its name.
Let's take the following example:
```java
public static void printInfo(String strg) {
System.out.println("This is some important information about " + strg);
}
```
I modified the method by adding the parameter `strg` to it, which is of the type `String`. What this means is that now, when calling the method, it expects you to give it a string that it can use to do stuff with (in this case, print it out).
If you add that parameter to the code we previously wrote, you might notice that your IDE is now displaying an error to you. You can't just do this anymore:
```java
public static void main(String[] args) {
printInfo();
}
```
As we just said, `printInfo` now wants us to give it a string whenever we call it. To give it that string, simply put it between the parentheses `()` of your method call like this:
```java
String someString = "Some String";
printInfo(someString);
// or, optionally, the shorter form:
printInfo("Some String");
```
Running your code now should cause `This is some important information about Some String` to be displayed in your console.
In case you're thinking "I've seen that somewhere before", then you would be correct: This is exactly the same thing you do when you try to print something out to the console: You call the `println` method and give it the text you want to print out as the parameter.
The cool thing is that a method can also accept *multiple* parameters, so multiple bits of data can be passed into it when calling it. Let's look at this example:
```java
public class Main {
public static void main(String[] args) {
printInfo("Some String", 5);
}
public static void printInfo(String strg, int amount) {
for (int i = 0; i < amount; i = i + 1) {
System.out.println("This is some important information about " + strg);
}
}
}
```
As you can see, the `printInfo` method now takes two parameters, separated by a comma `,`. The code inside of the method should already be familiar to you: It's a simple for loop that prints the same message `amount` times.
So now, we have something that also demonstrates pretty well the versatility of methods: We can now call `printInfo` with any information that will be printed any amount of times:
```java
printInfo("Some String", 5);
printInfo("Some other String", 10);
```
# Returning
Another thing that methods can do that is really useful is the ability to *return* certain data. What this means is that they can, after their execution finishes, take the data they created or modified and give it back to the caller:
```java
public class Main {
public static void main(String[] args) {
int tenSquared = square(10);
System.out.println(tenSquared); // prints 100
System.out.println(square(5)); // prints 25
}
public static int square(int i) {
return i * i;
}
}
```
As you can see, I created a method `square` that returns its parameter `i`, but squared. To make a method return a value, two things have to be done:
- The method's *return type* has to be declared. For methods that don't return anything, the return type is `void`. For methods that return something, just replace `void` with the *type of variable* that it returns.
- To actually return a specific value after it has been calculated, just type `return`, followed by the value and a semicolon `;`.
Now, this method can be used as if it were just another number: We can set variables to it and print it out. But instead of it being just another number, it actually executes the code inside it every time.
## Stopping execution
A thing that should be noted about returning is that any code that comes after a return statement will *not* be executed. In other words: After returning a value, a method will stop its execution, no matter what comes next.
```java
public static int square(int i) {
return i * i;
System.out.println("This will never be called");
}
```
In this specific case, that isn't really useful - why write code that can never be executed? But take this other example:
```java
public static boolean isIGreaterThanJ(int i, int j) {
if (i > j) {
return true;
}
System.out.println("i is NOT greater than j!");
return false;
}
```
In this example, the print statement will not be executed if `i` is greater than `j` despite the fact that it's not wrapped in an `else`, because the method gets returned out of before it can be called.
## Requirements
If you create a method that has a return type other than `void` (which, again, means "this method doesn't return anything"), then every possible path that the execution can take inside the method *needs* to return *something*.
What this means is that you can't have something like this:
```java
public static boolean isIGreaterThanJ(int i, int j) {
if (i > j) {
return true;
}
System.out.println("i is NOT greater than j!");
}
```
Because when `i` is *not* greater than `j`, the print statement will be executed but then, the method doesn't know what value to return, so this code will give out an error before it even tries to execute.
# Conclusion
So yea, that's pretty much everything important there is to know about (static) methods. In one of the next tutorials, we'll finally get into actual object orientation and we'll soon be taking a look at non-static methods, which are even more useful than static methods.
As a little exercise, you might want to create some methods with different return types and different parameters and call them. Have some fun!
I hope you enjoyed reading, and of course: Happy coding!
---
layout: blog
title: "☕ Java Tutorial, Part 3: (Static) Methods"
description: In this Java tutorial for beginners, we cover what (static) methods, parameters and return types are.
tags: [Programming]
discuss: https://twitter.com/Ellpeck/status/1182775985885847558
archived: true
---
I've been thinking about how to structure this tutorial a lot, and I decided to teach you all about methods *before* I get into object orientation, so you'll have to wait a little while longer before we get into the real nitty-gritty. To understand this one, you should've already read and understood tutorials 1 and 2. If you haven't, you can get to them by clicking the "Previous Post" button in the navigation bar.
So, here goes!
# Methods
Methods are a way, in programming, to move certain code into a different location where it's more organized and, more importantly, where it can be called multiple times from multiple locations, possibly with different data. You'll see what exactly that means in a minute.
Let's create a simple method in our main class:
```java
public class Main {
public static void main(String[] args) {
// I've omitted the code from the previous tutorials for readability
}
public static void printInfo() {
System.out.println("This is some important information!");
}
}
```
Let's take a look at the `printInfo` method I created. In this tutorial, we'll only be talking about `public` and `static` methods, so you'll just have to take that part for granted for now. Following that, you write `void` and then the name of your method (which can be anything you want), followed by parentheses `()` and then braces `{}`. Similarly to the `if` statement and the `for` loop, the method's content goes between those two curly braces, as I have done in this example with the print statement.
As you can see, all a method is is pretty much a collection of code. At this point, you might've already noticed that that is exactly what the `main` structure we've previously been writing all of our code into is: `main` is just another method.
To *call* a method, that is to have the code inside of it be executed, all you have to do is write the following:
```java
public class Main {
public static void main(String[] args) {
// I've omitted the code from the previous tutorials for readability
printInfo();
}
public static void printInfo() {
System.out.println("This is some important information!");
}
}
```
There you go.
## Variables
It should be noted at this point that variables which are declared *inside* of a method, like the ones we've been using in the first two tutorials, are known as `local variables`. What this means is that they only exist *inside* of the method they are declared in. Check out this example:
```java
// This method declares a variable i that is set to 0
public static void methodOne() {
int i = 0;
}
// This method declares a *different* variable i that is set to 1
public static void methodTwo() {
int i = 1;
}
// This method will cause an error if you paste it into your IDE:
// You cannot declare two variables with the same name in one method.
public static void erroringMethod() {
int i = 0;
int i = 1;
}
```
This same behavior also counts for `if`, `for` and any other curly braces `{}` that you see: They close off any variables which are created inside of them and make them available to only that location.
# Parameters
Now you might be wondering what the point of methods is if all they do is execute a predefined list of code. Well... you can actually make a method *accept* a set of data that they can use to do their processing. The data given to a method is called *parameters*, and these parameters are simply variables that you declare between the method's parentheses `()` right after its name.
Let's take the following example:
```java
public static void printInfo(String strg) {
System.out.println("This is some important information about " + strg);
}
```
I modified the method by adding the parameter `strg` to it, which is of the type `String`. What this means is that now, when calling the method, it expects you to give it a string that it can use to do stuff with (in this case, print it out).
If you add that parameter to the code we previously wrote, you might notice that your IDE is now displaying an error to you. You can't just do this anymore:
```java
public static void main(String[] args) {
printInfo();
}
```
As we just said, `printInfo` now wants us to give it a string whenever we call it. To give it that string, simply put it between the parentheses `()` of your method call like this:
```java
String someString = "Some String";
printInfo(someString);
// or, optionally, the shorter form:
printInfo("Some String");
```
Running your code now should cause `This is some important information about Some String` to be displayed in your console.
In case you're thinking "I've seen that somewhere before", then you would be correct: This is exactly the same thing you do when you try to print something out to the console: You call the `println` method and give it the text you want to print out as the parameter.
The cool thing is that a method can also accept *multiple* parameters, so multiple bits of data can be passed into it when calling it. Let's look at this example:
```java
public class Main {
public static void main(String[] args) {
printInfo("Some String", 5);
}
public static void printInfo(String strg, int amount) {
for (int i = 0; i < amount; i = i + 1) {
System.out.println("This is some important information about " + strg);
}
}
}
```
As you can see, the `printInfo` method now takes two parameters, separated by a comma `,`. The code inside of the method should already be familiar to you: It's a simple for loop that prints the same message `amount` times.
So now, we have something that also demonstrates pretty well the versatility of methods: We can now call `printInfo` with any information that will be printed any amount of times:
```java
printInfo("Some String", 5);
printInfo("Some other String", 10);
```
# Returning
Another thing that methods can do that is really useful is the ability to *return* certain data. What this means is that they can, after their execution finishes, take the data they created or modified and give it back to the caller:
```java
public class Main {
public static void main(String[] args) {
int tenSquared = square(10);
System.out.println(tenSquared); // prints 100
System.out.println(square(5)); // prints 25
}
public static int square(int i) {
return i * i;
}
}
```
As you can see, I created a method `square` that returns its parameter `i`, but squared. To make a method return a value, two things have to be done:
- The method's *return type* has to be declared. For methods that don't return anything, the return type is `void`. For methods that return something, just replace `void` with the *type of variable* that it returns.
- To actually return a specific value after it has been calculated, just type `return`, followed by the value and a semicolon `;`.
Now, this method can be used as if it were just another number: We can set variables to it and print it out. But instead of it being just another number, it actually executes the code inside it every time.
## Stopping execution
A thing that should be noted about returning is that any code that comes after a return statement will *not* be executed. In other words: After returning a value, a method will stop its execution, no matter what comes next.
```java
public static int square(int i) {
return i * i;
System.out.println("This will never be called");
}
```
In this specific case, that isn't really useful - why write code that can never be executed? But take this other example:
```java
public static boolean isIGreaterThanJ(int i, int j) {
if (i > j) {
return true;
}
System.out.println("i is NOT greater than j!");
return false;
}
```
In this example, the print statement will not be executed if `i` is greater than `j` despite the fact that it's not wrapped in an `else`, because the method gets returned out of before it can be called.
## Requirements
If you create a method that has a return type other than `void` (which, again, means "this method doesn't return anything"), then every possible path that the execution can take inside the method *needs* to return *something*.
What this means is that you can't have something like this:
```java
public static boolean isIGreaterThanJ(int i, int j) {
if (i > j) {
return true;
}
System.out.println("i is NOT greater than j!");
}
```
Because when `i` is *not* greater than `j`, the print statement will be executed but then, the method doesn't know what value to return, so this code will give out an error before it even tries to execute.
# Conclusion
So yea, that's pretty much everything important there is to know about (static) methods. In one of the next tutorials, we'll finally get into actual object orientation and we'll soon be taking a look at non-static methods, which are even more useful than static methods.
As a little exercise, you might want to create some methods with different return types and different parameters and call them. Have some fun!
I hope you enjoyed reading, and of course: Happy coding!

View file

@ -1,209 +1,216 @@
So you've gotten far enough into Java now that you'll finally be able to learn about what this "object orientation" jazz is that everyone keeps talking about. I feel like this is a somewhat complicated topic to explain, so bear with me.
# Classes
So far in the tutorial, we've only ever used classes as basic storage containers for variables and methods. Let's stick with that for another moment while we create a simple `Utility` class that has a method we might want to use throughout our program. We'll create it in a new file (Right-Click on `src`, `New`, `Java Class` in IntelliJ) like so:
```java
public class Utility {
}
```
As you can see, this structure is similar to that of our `Main` class: The word `public`, followed by the word `class`, followed by the name of the class (which, in 99% of cases, should match the file name), followed by curly braces `{}`, which, as you might expect by now, contain the content of our class.
So let's add a method to it that, I don't know, returns the absolute value of an integer passed to it. We'll call this one `abs`, for "absolute".[^1]
```java
public class Utility {
public static int abs(int num) {
if (num < 0) {
return -num;
} else {
return num;
}
}
}
```
By now, this code should be pretty easy for you to understand: We've added a method called `abs` that takes in an integer parameter called `num`, and if `num` is negative, then we return the negated value of num (`-num`).
Now, because this method is in a different class to the one we might want to use the method in (`Main` in our case), we have to explicitly mention the class the method is in by prefixing the class' name followed by a dot `.` when calling it, like so:
```java
public class Main {
public static void main(String[] args) {
// This is our usual main method
int abs = Utility.abs(-17);
System.out.println(abs);
}
}
```
# Objects
Let's finally move away from `static` methods and into object orientation.[^2] I'll give you a quick, made-by-me outline of what object orientation even *means*, based on an example that might be a bit easier for you to understand.
Let's imagine we're not talking about objects, but *tables*. When you say something like "A table has four legs", you're not talking about a *specific* table, but rather, you're talking about the *concept* of what a table is. In Java, this is what a class is. Now, when you say something like "This table has four legs" while pointing to the table on the other side of the room, then you're talking about a very specific table, not just a concept anymore. In Java, this is what an object is. When we now say "object oriented", we merely mean "code that makes use of objects."
Another way to think of it is that classes are *blueprints* for certain things, and objects are the actual constructions that were created by following those blueprints.
## Creating Objects
So let's stick with this table example for now. First, we'll create a class called `Table` like so:
```java
public class Table {
}
```
So now, we have a very basic blueprint of a table.
Now, let's go back into our main class and actually create a *specific* table like so:
```java
public class Main {
public static void main(String[] args) {
// This is our usual main method
Table myTable;
myTable = new Table();
}
}
```
As you can see, the structure of line 5 is the same as how we usually declare variables: `Table` is the variable type (this is where we usually put `int` or `String`), and `myTable` is the name of the variable. Line 6 is where the new stuff happens, however: We set the variable's *value* to a newly created *instance* of the `Table` class. This is us creating a specific table following the blueprint that is the `Table` class: We write `new`, then the class' name, and then opening and closing parentheses `()`.
# Fields
What we have so far isn't really that interesting yet - our table doesn't really have any properties yet. But now, we can actually add *fields* to our class: Certain variables that each object will share, but whose values can also be *different* for each object. Fields are declared like normal variables, but are placed at the top of a class, prefixed with the `public` keyword like so:
```java
public class Table {
public int legAmount;
}
```
Now, each table *instance* can have its amount of legs modified as well as displayed like so:
```java
Table myTable = new Table();
myTable.legAmount = 4;
System.out.println("My table has " + myTable.legAmount + " legs");
```
As you can see, to access an object's fields, all you have to do is write the variable name, followed by a dot `.`, followed by the field's name.
The important thing to know here is that this variable is now unique for *every table instance you create*, meaning the following code...
```java
Table myTable = new Table();
Table myOtherTable = new Table();
myTable.legAmount = 4;
myOtherTable.legAmount = 25;
System.out.println("My table has " + myTable.legAmount + " legs");
System.out.println("My other table has " + myOtherTable.legAmount + " legs");
```
...will print out `My table has 4 legs`, followed by `My other table has 25 legs`.
# Methods
Obviously, you already know what methods are, but we can finally get rid of the `static` keyword for those as well. Similarly to what we can do with fields, we can also add methods to classes that can do unique things based on the object they're being called for. Let's take a look at the following code:
```java
public class Table {
public int legAmount;
public void printLegAmount() {
System.out.println("I have a certain amount of legs");
}
}
```
As you can see, I've added a (non-static) method to this class called `printLegAmount`. Now we can change the code in our `Main` class to just call that method instead:
```java
Table myTable = new Table();
myTable.legAmount = 4;
myTable.printLegAmount();
```
## The `this` keyword
Now that specific example isn't really that useful yet, because every single table we create will print out `I have a certain amount of legs`, when it would really be nicer if the table printed out its amount of legs stored in the `legAmount` variable.
That's exactly what the `this` keyword is for.[^3] Let's modify our method from before:
```java
public class Table {
public int legAmount;
public void printLegAmount() {
System.out.println("I have " + this.legAmount + " legs");
}
}
```
As you can see, `this` is being used here similarly to how we were using `myTable.legAmount` earlier. That has a good reason: `this` also refers to an object. However, the object that `this` refers to is *the current one*. When calling a method like `printLegAmount` for a specific object (`myTable` in our case), the `this` keyword will always refer to that object.
So if we have code like this...
```java
Table myTable = new Table();
Table myOtherTable = new Table();
myTable.legAmount = 4;
myOtherTable.legAmount = 25;
myTable.printLegAmount();
myOtherTable.printLegAmount();
```
...then the result will be the same as before: `My table has 4 legs`, followed by `My other table has 25 legs`, because in `myTable.printLegAmount()`, `this` will be `myTable`, and in `myOtherTable.printLegAmount()`, `this` will be `myOtherTable`.
# Constructors
Constructors are a special kind of method in Java. You can't really execute them manually, but instead, they're automatically called every time a new instance is created (so every time you use the `new` keyword).
Let's look at the following example, which adds a constructor to our `Table` class that automatically sets the `legAmount` variable to 4.
```java
public class Table {
public int legAmount;
// The constructor
public Table() {
this.legAmount = 4;
}
public void printLegAmount() {
System.out.println("I have " + this.legAmount + " legs");
}
}
```
As you can see, a constructor is different from other methods in that it *doesn't have a name*, and that its return type is *the same as the class it is in*.
So now, any instance of `Table` that we create will automatically have a leg amount of 4:
```java
Table myTable = new Table();
myTable.printLegAmount(); // "I have 4 legs"
```
## Constructor parameters
Just like in any other method, we can add a set of variables that the constructor accepts (parameters). In our case, we could make a table request a `legAmount` when it's initialized as follows:
```java
// The constructor
public Table(int legs) {
this.legAmount = legs;
}
```
Now, when creating a new instance of `Table`, we have to actually give it a leg amount that it should start with:
```java
Table myTable = new Table(4);
```
# So what about `static`?
As you have learned throughout this tutorial, non-static methods and variables inside of classes have a key property: They're unique for every object that is created of that class. So why have we been using `static` throughout both our `Main` and our `Utility` classes?
The `static` keyword tells any field or method that it should *not* be unique for every object that is created. Even if we created an instance of our `Main` class (`new Main()`), for example, the `main` method would not be unique for that instance, because it is marked with the `static` keyword. This also means that using the `this` keyword would make no sense in a static method (which object would it reference?), which is why doing so doesn't work.
Similarly, if we made the `abs` method in our `Utility` class non-static, then we could *not* access it simply by typing `Utility.abs()`, because the method would be unique for every object that is created of the class. We *could* then use the `abs` method if we first created an instance of the `Utility` class (`new Utility()`).
# Conclusion
So yea, that's quite a lot of information for you to process. The introduction to objects is a pretty big step, but it also allows for a lot of cool new stuff that you can learn. In the next couple tutorials, we'll expand on the concept of objects and learn some more stuff that you can do with them to make them even more useful.
For now, though, I'll leave you with this little exercise you can do to facilitate what you learned about objects and to get a better understanding of how useful they are:[^4]
> Let's imagine you're managing a small car dealership. You want to have a way of managing all of the cars you have in stock. This includes keeping track of their brand names, their horsepower, their license plate texts and their mileage. Currently, you have four different cars in stock, and you want your program to print out all of the information about all of the cars.
If you're stuck, you can [get some hints](https://gist.github.com/Ellpeck/462022597659f554fdd75663359480d3) or [look at my solution](https://gist.github.com/Ellpeck/7a0f31306d05473c10e8bca1685510a4).
Next time, we'll be talking about arrays and lists.[^5] Happy coding!
***
[^1]: Java's default `Math` class already has an `abs` method that does this same thing, but for the sake of this tutorial, we'll ignore it (because I can't think of any other utility method to add, in all honesty).
[^2]: Note that I explain why we're omitting the `static` keyword from now on a bit later in this tutorial.
[^3]: In a lot of cases, using the `this` keyword is actually optional and it can just be left out altogether. However, I personally think that it increases code readability quite a bit and that it helps a lot with understanding how objects work, so I advise you to use it.
[^4]: This example is one that almost every instructor uses. It might be a bit boring and ridiculous, but it's simple and helps understanding the concept, so I also like using it when teaching people how to code.
[^5]: Note that there is also another huge topic to talk about when it comes to object orientation, which is *pointers* and how Java manages objects and their assignment to variables (including pass-by-reference and the like). I'll cover that soon. I promise. <3
---
layout: blog
title: "☕ Java Tutorial, Part 4: Classes and Objects"
description: In this Java tutorial for beginners, we cover the basics of creating classes with a constructor, some fields and some methods and creating objects of them.
tags: [Programming]
discuss: https://twitter.com/Ellpeck/status/1183857460660101133
archived: true
---
So you've gotten far enough into Java now that you'll finally be able to learn about what this "object orientation" jazz is that everyone keeps talking about. I feel like this is a somewhat complicated topic to explain, so bear with me.
# Classes
So far in the tutorial, we've only ever used classes as basic storage containers for variables and methods. Let's stick with that for another moment while we create a simple `Utility` class that has a method we might want to use throughout our program. We'll create it in a new file (Right-Click on `src`, `New`, `Java Class` in IntelliJ) like so:
```java
public class Utility {
}
```
As you can see, this structure is similar to that of our `Main` class: The word `public`, followed by the word `class`, followed by the name of the class (which, in 99% of cases, should match the file name), followed by curly braces `{}`, which, as you might expect by now, contain the content of our class.
So let's add a method to it that, I don't know, returns the absolute value of an integer passed to it. We'll call this one `abs`, for "absolute".[^1]
```java
public class Utility {
public static int abs(int num) {
if (num < 0) {
return -num;
} else {
return num;
}
}
}
```
By now, this code should be pretty easy for you to understand: We've added a method called `abs` that takes in an integer parameter called `num`, and if `num` is negative, then we return the negated value of num (`-num`).
Now, because this method is in a different class to the one we might want to use the method in (`Main` in our case), we have to explicitly mention the class the method is in by prefixing the class' name followed by a dot `.` when calling it, like so:
```java
public class Main {
public static void main(String[] args) {
// This is our usual main method
int abs = Utility.abs(-17);
System.out.println(abs);
}
}
```
# Objects
Let's finally move away from `static` methods and into object orientation.[^2] I'll give you a quick, made-by-me outline of what object orientation even *means*, based on an example that might be a bit easier for you to understand.
Let's imagine we're not talking about objects, but *tables*. When you say something like "A table has four legs", you're not talking about a *specific* table, but rather, you're talking about the *concept* of what a table is. In Java, this is what a class is. Now, when you say something like "This table has four legs" while pointing to the table on the other side of the room, then you're talking about a very specific table, not just a concept anymore. In Java, this is what an object is. When we now say "object oriented", we merely mean "code that makes use of objects."
Another way to think of it is that classes are *blueprints* for certain things, and objects are the actual constructions that were created by following those blueprints.
## Creating Objects
So let's stick with this table example for now. First, we'll create a class called `Table` like so:
```java
public class Table {
}
```
So now, we have a very basic blueprint of a table.
Now, let's go back into our main class and actually create a *specific* table like so:
```java
public class Main {
public static void main(String[] args) {
// This is our usual main method
Table myTable;
myTable = new Table();
}
}
```
As you can see, the structure of line 5 is the same as how we usually declare variables: `Table` is the variable type (this is where we usually put `int` or `String`), and `myTable` is the name of the variable. Line 6 is where the new stuff happens, however: We set the variable's *value* to a newly created *instance* of the `Table` class. This is us creating a specific table following the blueprint that is the `Table` class: We write `new`, then the class' name, and then opening and closing parentheses `()`.
# Fields
What we have so far isn't really that interesting yet - our table doesn't really have any properties yet. But now, we can actually add *fields* to our class: Certain variables that each object will share, but whose values can also be *different* for each object. Fields are declared like normal variables, but are placed at the top of a class, prefixed with the `public` keyword like so:
```java
public class Table {
public int legAmount;
}
```
Now, each table *instance* can have its amount of legs modified as well as displayed like so:
```java
Table myTable = new Table();
myTable.legAmount = 4;
System.out.println("My table has " + myTable.legAmount + " legs");
```
As you can see, to access an object's fields, all you have to do is write the variable name, followed by a dot `.`, followed by the field's name.
The important thing to know here is that this variable is now unique for *every table instance you create*, meaning the following code...
```java
Table myTable = new Table();
Table myOtherTable = new Table();
myTable.legAmount = 4;
myOtherTable.legAmount = 25;
System.out.println("My table has " + myTable.legAmount + " legs");
System.out.println("My other table has " + myOtherTable.legAmount + " legs");
```
...will print out `My table has 4 legs`, followed by `My other table has 25 legs`.
# Methods
Obviously, you already know what methods are, but we can finally get rid of the `static` keyword for those as well. Similarly to what we can do with fields, we can also add methods to classes that can do unique things based on the object they're being called for. Let's take a look at the following code:
```java
public class Table {
public int legAmount;
public void printLegAmount() {
System.out.println("I have a certain amount of legs");
}
}
```
As you can see, I've added a (non-static) method to this class called `printLegAmount`. Now we can change the code in our `Main` class to just call that method instead:
```java
Table myTable = new Table();
myTable.legAmount = 4;
myTable.printLegAmount();
```
## The `this` keyword
Now that specific example isn't really that useful yet, because every single table we create will print out `I have a certain amount of legs`, when it would really be nicer if the table printed out its amount of legs stored in the `legAmount` variable.
That's exactly what the `this` keyword is for.[^3] Let's modify our method from before:
```java
public class Table {
public int legAmount;
public void printLegAmount() {
System.out.println("I have " + this.legAmount + " legs");
}
}
```
As you can see, `this` is being used here similarly to how we were using `myTable.legAmount` earlier. That has a good reason: `this` also refers to an object. However, the object that `this` refers to is *the current one*. When calling a method like `printLegAmount` for a specific object (`myTable` in our case), the `this` keyword will always refer to that object.
So if we have code like this...
```java
Table myTable = new Table();
Table myOtherTable = new Table();
myTable.legAmount = 4;
myOtherTable.legAmount = 25;
myTable.printLegAmount();
myOtherTable.printLegAmount();
```
...then the result will be the same as before: `My table has 4 legs`, followed by `My other table has 25 legs`, because in `myTable.printLegAmount()`, `this` will be `myTable`, and in `myOtherTable.printLegAmount()`, `this` will be `myOtherTable`.
# Constructors
Constructors are a special kind of method in Java. You can't really execute them manually, but instead, they're automatically called every time a new instance is created (so every time you use the `new` keyword).
Let's look at the following example, which adds a constructor to our `Table` class that automatically sets the `legAmount` variable to 4.
```java
public class Table {
public int legAmount;
// The constructor
public Table() {
this.legAmount = 4;
}
public void printLegAmount() {
System.out.println("I have " + this.legAmount + " legs");
}
}
```
As you can see, a constructor is different from other methods in that it *doesn't have a name*, and that its return type is *the same as the class it is in*.
So now, any instance of `Table` that we create will automatically have a leg amount of 4:
```java
Table myTable = new Table();
myTable.printLegAmount(); // "I have 4 legs"
```
## Constructor parameters
Just like in any other method, we can add a set of variables that the constructor accepts (parameters). In our case, we could make a table request a `legAmount` when it's initialized as follows:
```java
// The constructor
public Table(int legs) {
this.legAmount = legs;
}
```
Now, when creating a new instance of `Table`, we have to actually give it a leg amount that it should start with:
```java
Table myTable = new Table(4);
```
# So what about `static`?
As you have learned throughout this tutorial, non-static methods and variables inside of classes have a key property: They're unique for every object that is created of that class. So why have we been using `static` throughout both our `Main` and our `Utility` classes?
The `static` keyword tells any field or method that it should *not* be unique for every object that is created. Even if we created an instance of our `Main` class (`new Main()`), for example, the `main` method would not be unique for that instance, because it is marked with the `static` keyword. This also means that using the `this` keyword would make no sense in a static method (which object would it reference?), which is why doing so doesn't work.
Similarly, if we made the `abs` method in our `Utility` class non-static, then we could *not* access it simply by typing `Utility.abs()`, because the method would be unique for every object that is created of the class. We *could* then use the `abs` method if we first created an instance of the `Utility` class (`new Utility()`).
# Conclusion
So yea, that's quite a lot of information for you to process. The introduction to objects is a pretty big step, but it also allows for a lot of cool new stuff that you can learn. In the next couple tutorials, we'll expand on the concept of objects and learn some more stuff that you can do with them to make them even more useful.
For now, though, I'll leave you with this little exercise you can do to facilitate what you learned about objects and to get a better understanding of how useful they are:[^4]
> Let's imagine you're managing a small car dealership. You want to have a way of managing all of the cars you have in stock. This includes keeping track of their brand names, their horsepower, their license plate texts and their mileage. Currently, you have four different cars in stock, and you want your program to print out all of the information about all of the cars.
If you're stuck, you can [get some hints](https://gist.github.com/Ellpeck/462022597659f554fdd75663359480d3) or [look at my solution](https://gist.github.com/Ellpeck/7a0f31306d05473c10e8bca1685510a4).
Next time, we'll be talking about arrays and lists.[^5] Happy coding!
[^1]: Java's default `Math` class already has an `abs` method that does this same thing, but for the sake of this tutorial, we'll ignore it (because I can't think of any other utility method to add, in all honesty).
[^2]: Note that I explain why we're omitting the `static` keyword from now on a bit later in this tutorial.
[^3]: In a lot of cases, using the `this` keyword is actually optional and it can just be left out altogether. However, I personally think that it increases code readability quite a bit and that it helps a lot with understanding how objects work, so I advise you to use it.
[^4]: This example is one that almost every instructor uses. It might be a bit boring and ridiculous, but it's simple and helps understanding the concept, so I also like using it when teaching people how to code.
[^5]: Note that there is also another huge topic to talk about when it comes to object orientation, which is *pointers* and how Java manages objects and their assignment to variables (including pass-by-reference and the like). I'll cover that soon. I promise. <3

View file

@ -1,166 +1,173 @@
After that complicated stuff we did in the last tutorial, how about we take it down a notch in this one and talk about some additional things that I haven't mentioned this far, but that will still be very useful to you as a programmer. Most of these things won't really have much connection to each other, but I'll give you an example at the end of this tutorial that combines some of them into a single use case.
# Shorthands
Java has some shorthands to make addition and other mathematical operations faster. As there isn't really that much to explain (they're really just vocabulary), here's a list of them:
```java
// These two statements are equivalent
// (This also works with all other math operators)
i = i + 2;
i += 2;
// When adding or subtracting 1, you can just do this:
i++;
i--;
```
# Primitive Types
Primitive types (also called *native types*) are variable types in a language that are baked into the very foundation of the language. That means they don't derive from a class, but they just exist as part of the language itself. The two primitive types you already know are `int` (32 bit integers) and `boolean` (`true` and `false`). In Java, primitive types always start with a lowercase letter (instead of an uppercase letter for classes).
Along with `int` and `boolean`, there are some more primitive types that could come in useful when programming. Here are the ones I haven't mentioned yet, quickly summed up:
```java
// "char" represents a single character.
// Characters use single quotes ' rather than double quotes " (which are reserved for strings).
char character = 'A';
char fourthChar = "Hello World".charAt(4); // o
// "double" represents a 64bit floating point number
// which is a number with a decimal point
// It's called a double because it has double the precision of float (see below)
double d = 3.14;
// "float" represents a 32bit floating point number (less precise than double).
// To differentiate floats from doubles and ints, you have to append an "F".
float f = 12.5F;
// "long" represents a 64bit integer (which can store a lot higher numbers than int)
// To differentiate longs from ints, you have to append an "L".
long l = 9223372036854775807L;
// "short" represents a 16bit integer
short s = 32767;
// "byte" represents an 8bit integer
byte b = 127;
```
## What about `String`?
Oh boy. Strings are quite a mess in Java, because they *behave* like primitive types (more on that later), but they really *are* classes. Also, you can just initialize a string using quotes `""` instead of having to call `new String()`, which makes them behave differently than *any other class* in the language. This complication is also the reason that you can't just compare two strings using `==`, but you have to use `.equals()` instead. Meh.
## Pass-by-reference vs. pass-by-value
An important thing to know about primitive types compared to objects is the behavior they show when being passed into a method that accepts parameters. Let's look at the following code as an example:
```java
public class Main {
public static void main(String[] args) {
int i = 10;
addOne(i);
System.out.println(i); // still 10, what gives?
}
private static void addOne(int i){
i++;
}
}
```
As you can see, the `addOne` method adds one to the parameter passed into it. But despite that, the code in our `main` method still prints out the number 10. This happens because primitive types (and `String`, hurr durr) use what is called *pass-by-value*: When calling `addOne`, its `i` variable is just set to the *value* of the `i` variable from our `main` method. It doesn't know about the actual *variable* we pass in, just its *value*.
This behavior, however, is different when using Objects. Let's rewrite the code above to use a custom class that contains an `int` field:
```java
// Thing.java
public class Thing {
public int i;
public Thing(int i) {
this.i = i;
}
}
// Main.java
public class Main {
public static void main(String[] args) {
Thing thing = new Thing(10);
addOne(thing);
System.out.println(thing.i); // 11
}
private static void addOne(Thing thing) {
thing.i++;
}
}
```
As you can see, the program now prints out 11 instead of 10. That's because of something Java uses called *pointers*[^1]. Our `thing` variable in the `main` method doesn't really store the actual *instance*, but instead, it stores the location of that instance in memory. So when passing that variable into the `addOne` method, all we pass is the information "The thing we're trying to modify sits in this part of memory, so modify the data there, please." That's why, when we then change the `Thing`'s `i` variable, it's also modified outside of the method.
# `null`
Any variables that can store objects rather than primitive types (our `thing` variable from before, for example) can have a state where they don't point to an object, but instead, point to nothing. This "nothing" is called `null`.
```java
Thing noThing = null;
```
Using `null` can be useful in a lot of cases; however, it can also be quite dangerous: Trying to call a method or interact with a field of a variable that is `null` causes the program to crash:
```java
Thing noThing = null;
System.out.println(noThing.i); // crashes
```
Just like any other objects, you can use `null` in comparisons:
```java
if (noThing == null) {
System.out.println("I don't have a thing!");
}
```
# Arrays and Lists
Arrays and Lists are two data types that you'll be using a lot of, because they're super useful. Their names kind of already give away what they do, but in case they don't mean anything to you: Arrays and lists are two ways to store multiple pieces of data easily without having to manage each piece on its own. This can be useful for stuff like *students in a classroom*, where you won't really know for certain beforehand how many students will be there at any given day. The difference between Arrays and Lists is that the former has a *fixed size*, while the latter has a *dynamic size*.
## Arrays
Let's take a look at arrays first. An array has a fixed type, meaning that the objects in it can only be of the type you specify when creating the array. The variable type of an array of strings, for example, is written as `String[]`. To initialize an array, you have two options: Either you create an empty array based on a fixed size (where every slot will contain `null` to start with), or you create an array that already has stuff in it:
```java
int[] numbers = new int[10]; // empty array with 10 slots
String[] names = new String[]{"Justus", "Peter", "Bob"};
```
You can query and modify the data in an array using array brackets `[]` like so:
```java
String justus = names[0]; // get the 0th entry
names[1] = "Peter Shaw"; // modify the 1st entry
```
Note that, in Java, the first slot in an array has an index of 0, which means that the last slot of an array always has the index `length-1`. Every array has a `length` field which stores the amount of slots an array has, meaning you can query the length of `numbers` by writing `numbers.length`.
## Lists
Lists[^2] work much in the same way, with the main difference being the way you identify them. Lists use something called *generic types*[^3] to allow you to also give them a fixed type. The variable type of a list of strings, for example, is written as `ArrayList<String>`, using angle brackets `<>` (which are really just "greater than" and "less than" signs). To create a list, simply call its constructor using `new`:
```java
ArrayList<Integer> numbers = new ArrayList<>();
ArrayList<String> names = new ArrayList<>();
```
Note that, to create a list containing a primitive type, you need to specify its *wrapper class* instead of the native type itself[^4]. The wrapper class for `int` is `Integer`, and the wrapper classes for the other types are just their names with an uppercase first letter (`Boolean`, for example).
Also note that, when copying or writing this code in your IDE, it will automatically add `import` statements to the top of the class. All they do is make classes from other locations available to the current class, but you don't have to worry about them too much.
You can query and modify the data in a list using the methods from the `ArrayList` class:
```java
names.add("Justus"); // add entries
names.add("Peter");
names.add("Bob");
String justus = names.get(0); // get the 0th entry
names.set(1, "Peter Shaw"); // modify the 1st entry
// the length of a list can be read using the size() method
int length = names.size();
```
Note that, when trying to access an index of a list or an array that is either less than 0 or greater than or equal to its length, your program will crash, so be careful.
# Conclusion
So today, you learned some additional useful things about Java that I didn't really get a chance to mention at an earlier point. So let's write some code that makes use of them! If you want, you can try to solve the following problem, which is an extension of the problem from the previous tutorial:
> Let's imagine you're managing a small car dealership. You want to have a way of managing all of the cars you have in stock. This includes keeping track of their brand names, their horsepower, their license plate texts and their mileage. Currently, you have 15 parking spots for cars you can sell, 4 of which are already occupied with cars. Additionally, you keep track of a list of all the customers you have had so far (namely, you store their first and last names as well as the city they live in). At the current time, you've already had 3 customers. Additionally, you want to have a way of adding a new car to the first available parking spot, as well as a way to store the data of a new customer easily.
If you're stuck, you can [get some hints](https://gist.github.com/Ellpeck/3bdc69845d0d4c0e9511511dd06cfdc1) or [look at my solution](https://gist.github.com/Ellpeck/595b929ce666fca6fb0ed792ca98d71e).
Happy coding!
***
[^1]: Java's pointers work a lot differently from pointers in lower-level languages like C, because they're implicit: You don't create or manage them yourself. They're still called pointers though, so yea.
[^2]: Java has multiple types of lists (including, but not limited to `ArrayList` and `LinkedList`), but for the purpose of this tutorial, we'll only be looking at `ArrayList` since it's the most commonly used one and it also goes hand in hand with arrays.
[^3]: More on those in a later tutorial, probably. They're pretty useful.
[^4]: Which is also just another annoying property of Java that could've been implemented a lot better (like in C#), but oh well, it is what it is.
---
layout: blog
title: "☕ Java Tutorial, Part 5: Things I Left Out So Far"
description: In this Java tutorial for beginners, we cover some shorthands, some more data types, the difference between pass-by-reference and pass-by-value, null, as well as arrays and lists.
tags: [Programming]
discuss: https://twitter.com/Ellpeck/status/1184894859133509632
archived: true
---
After that complicated stuff we did in the last tutorial, how about we take it down a notch in this one and talk about some additional things that I haven't mentioned this far, but that will still be very useful to you as a programmer. Most of these things won't really have much connection to each other, but I'll give you an example at the end of this tutorial that combines some of them into a single use case.
# Shorthands
Java has some shorthands to make addition and other mathematical operations faster. As there isn't really that much to explain (they're really just vocabulary), here's a list of them:
```java
// These two statements are equivalent
// (This also works with all other math operators)
i = i + 2;
i += 2;
// When adding or subtracting 1, you can just do this:
i++;
i--;
```
# Primitive Types
Primitive types (also called *native types*) are variable types in a language that are baked into the very foundation of the language. That means they don't derive from a class, but they just exist as part of the language itself. The two primitive types you already know are `int` (32 bit integers) and `boolean` (`true` and `false`). In Java, primitive types always start with a lowercase letter (instead of an uppercase letter for classes).
Along with `int` and `boolean`, there are some more primitive types that could come in useful when programming. Here are the ones I haven't mentioned yet, quickly summed up:
```java
// "char" represents a single character.
// Characters use single quotes ' rather than double quotes " (which are reserved for strings).
char character = 'A';
char fourthChar = "Hello World".charAt(4); // o
// "double" represents a 64bit floating point number
// which is a number with a decimal point
// It's called a double because it has double the precision of float (see below)
double d = 3.14;
// "float" represents a 32bit floating point number (less precise than double).
// To differentiate floats from doubles and ints, you have to append an "F".
float f = 12.5F;
// "long" represents a 64bit integer (which can store a lot higher numbers than int)
// To differentiate longs from ints, you have to append an "L".
long l = 9223372036854775807L;
// "short" represents a 16bit integer
short s = 32767;
// "byte" represents an 8bit integer
byte b = 127;
```
## What about `String`?
Oh boy. Strings are quite a mess in Java, because they *behave* like primitive types (more on that later), but they really *are* classes. Also, you can just initialize a string using quotes `""` instead of having to call `new String()`, which makes them behave differently than *any other class* in the language. This complication is also the reason that you can't just compare two strings using `==`, but you have to use `.equals()` instead. Meh.
## Pass-by-reference vs. pass-by-value
An important thing to know about primitive types compared to objects is the behavior they show when being passed into a method that accepts parameters. Let's look at the following code as an example:
```java
public class Main {
public static void main(String[] args) {
int i = 10;
addOne(i);
System.out.println(i); // still 10, what gives?
}
private static void addOne(int i){
i++;
}
}
```
As you can see, the `addOne` method adds one to the parameter passed into it. But despite that, the code in our `main` method still prints out the number 10. This happens because primitive types (and `String`, hurr durr) use what is called *pass-by-value*: When calling `addOne`, its `i` variable is just set to the *value* of the `i` variable from our `main` method. It doesn't know about the actual *variable* we pass in, just its *value*.
This behavior, however, is different when using Objects. Let's rewrite the code above to use a custom class that contains an `int` field:
```java
// Thing.java
public class Thing {
public int i;
public Thing(int i) {
this.i = i;
}
}
// Main.java
public class Main {
public static void main(String[] args) {
Thing thing = new Thing(10);
addOne(thing);
System.out.println(thing.i); // 11
}
private static void addOne(Thing thing) {
thing.i++;
}
}
```
As you can see, the program now prints out 11 instead of 10. That's because of something Java uses called *pointers*[^1]. Our `thing` variable in the `main` method doesn't really store the actual *instance*, but instead, it stores the location of that instance in memory. So when passing that variable into the `addOne` method, all we pass is the information "The thing we're trying to modify sits in this part of memory, so modify the data there, please." That's why, when we then change the `Thing`'s `i` variable, it's also modified outside of the method.
# `null`
Any variables that can store objects rather than primitive types (our `thing` variable from before, for example) can have a state where they don't point to an object, but instead, point to nothing. This "nothing" is called `null`.
```java
Thing noThing = null;
```
Using `null` can be useful in a lot of cases; however, it can also be quite dangerous: Trying to call a method or interact with a field of a variable that is `null` causes the program to crash:
```java
Thing noThing = null;
System.out.println(noThing.i); // crashes
```
Just like any other objects, you can use `null` in comparisons:
```java
if (noThing == null) {
System.out.println("I don't have a thing!");
}
```
# Arrays and Lists
Arrays and Lists are two data types that you'll be using a lot of, because they're super useful. Their names kind of already give away what they do, but in case they don't mean anything to you: Arrays and lists are two ways to store multiple pieces of data easily without having to manage each piece on its own. This can be useful for stuff like *students in a classroom*, where you won't really know for certain beforehand how many students will be there at any given day. The difference between Arrays and Lists is that the former has a *fixed size*, while the latter has a *dynamic size*.
## Arrays
Let's take a look at arrays first. An array has a fixed type, meaning that the objects in it can only be of the type you specify when creating the array. The variable type of an array of strings, for example, is written as `String[]`. To initialize an array, you have two options: Either you create an empty array based on a fixed size (where every slot will contain `null` to start with), or you create an array that already has stuff in it:
```java
int[] numbers = new int[10]; // empty array with 10 slots
String[] names = new String[]{"Justus", "Peter", "Bob"};
```
You can query and modify the data in an array using array brackets `[]` like so:
```java
String justus = names[0]; // get the 0th entry
names[1] = "Peter Shaw"; // modify the 1st entry
```
Note that, in Java, the first slot in an array has an index of 0, which means that the last slot of an array always has the index `length-1`. Every array has a `length` field which stores the amount of slots an array has, meaning you can query the length of `numbers` by writing `numbers.length`.
## Lists
Lists[^2] work much in the same way, with the main difference being the way you identify them. Lists use something called *generic types*[^3] to allow you to also give them a fixed type. The variable type of a list of strings, for example, is written as `ArrayList<String>`, using angle brackets `<>` (which are really just "greater than" and "less than" signs). To create a list, simply call its constructor using `new`:
```java
ArrayList<Integer> numbers = new ArrayList<>();
ArrayList<String> names = new ArrayList<>();
```
Note that, to create a list containing a primitive type, you need to specify its *wrapper class* instead of the native type itself[^4]. The wrapper class for `int` is `Integer`, and the wrapper classes for the other types are just their names with an uppercase first letter (`Boolean`, for example).
Also note that, when copying or writing this code in your IDE, it will automatically add `import` statements to the top of the class. All they do is make classes from other locations available to the current class, but you don't have to worry about them too much.
You can query and modify the data in a list using the methods from the `ArrayList` class:
```java
names.add("Justus"); // add entries
names.add("Peter");
names.add("Bob");
String justus = names.get(0); // get the 0th entry
names.set(1, "Peter Shaw"); // modify the 1st entry
// the length of a list can be read using the size() method
int length = names.size();
```
Note that, when trying to access an index of a list or an array that is either less than 0 or greater than or equal to its length, your program will crash, so be careful.
# Conclusion
So today, you learned some additional useful things about Java that I didn't really get a chance to mention at an earlier point. So let's write some code that makes use of them! If you want, you can try to solve the following problem, which is an extension of the problem from the previous tutorial:
> Let's imagine you're managing a small car dealership. You want to have a way of managing all of the cars you have in stock. This includes keeping track of their brand names, their horsepower, their license plate texts and their mileage. Currently, you have 15 parking spots for cars you can sell, 4 of which are already occupied with cars. Additionally, you keep track of a list of all the customers you have had so far (namely, you store their first and last names as well as the city they live in). At the current time, you've already had 3 customers. Additionally, you want to have a way of adding a new car to the first available parking spot, as well as a way to store the data of a new customer easily.
If you're stuck, you can [get some hints](https://gist.github.com/Ellpeck/3bdc69845d0d4c0e9511511dd06cfdc1) or [look at my solution](https://gist.github.com/Ellpeck/595b929ce666fca6fb0ed792ca98d71e).
Happy coding!
[^1]: Java's pointers work a lot differently from pointers in lower-level languages like C, because they're implicit: You don't create or manage them yourself. They're still called pointers though, so yea.
[^2]: Java has multiple types of lists (including, but not limited to `ArrayList` and `LinkedList`), but for the purpose of this tutorial, we'll only be looking at `ArrayList` since it's the most commonly used one and it also goes hand in hand with arrays.
[^3]: More on those in a later tutorial, probably. They're pretty useful.
[^4]: Which is also just another annoying property of Java that could've been implemented a lot better (like in C#), but oh well, it is what it is.

View file

@ -1,21 +1,30 @@
So some of you might be disappointed that this *isn't* the next Java tutorial. Well, today and the last couple of days, I haven't been feeling like working on those, because.. I've been depressed again. I never know how to describe it, or how to say it without seeming like it's just.. something I say when I'm too lazy to work on things.
This post will probably be a little all over the place, because that's also the mood that my head is currently in, but I'll try to explain to you what it feels like to me to have a low low follow a high high, mood-wise.
# Highs
When I say that I'm depressed, it means something different than when I say that I'm suffering from depression. Because, for me, depression isn't a constant state. Before depression hits, I have long periods of highs, long periods of what non-depressed people would probably just call.. normal moods.
I get up in the mornings, I enjoy breakfast, I go to University, I do my daily chores, I sit down at my computer, I code, I play some games, and then I go to bed. Normal days, really.
And if you don't suffer from depression, then you might think "Well, what's so special about that?", and the annoying thing is: That's *exactly* what I also think when I'm experiencing a high. I take it for granted, and I never even stop to think about how great it is that I'm currently feeling normal about my life.
# Lows
And then a low hits. And the longer the high has been going on for, the longer I was happy for, the harder it hits. Right now, I've been feeling low for two days, after a high that lasted almost *a month*. When I'm going through a low phase like now, my day works a lot differently.
I wake up at noon. I stay in bed. I check my phone. Hours pass. I think to myself "What am I doing?" I check my phone. After a while, I force myself to get up; I might even take a shower. I find some cookies in the back of my fridge; those'll be good enough for breakfast, I think to myself. I sit down at my computer. I watch some videos. "What am I doing?" Maybe some more videos. "What am I doing? I don't know what to do." I start Minecraft. "This sounds like fun!" I play for five minutes. I close the game again. "What am I doing?" I watch some videos. I start The Sims 4. I play for half an hour, and then I close the game again. "What am I doing?" I watch some more YouTube videos. I listen to some music. Should I do my coursework now? Not in the mood. I open IntelliJ. Let's work on some code. I glance at a couple of files, and then I close it again. "What am I doing?"
Take that whole description, and slow it down by about 200%. That's what a low day feels like to me. I feel like every single hour; every single *minute* even, is filled with doubt and emptiness and just... nothing.
This is so hard to explain for me because I also don't.. see myself in these low phases. I don't look at myself. I don't observe what I'm doing. It's like I'm not even there; it's like I'm not inside my own body, doing these activities with it. It's like it's just doing everything on some sort of weird auto-pilot mode.
So until that passes, I probably won't be working on any tutorials, or anything, really. Let's hope it passes fairly soon, because I'm honestly sick of forcing myself to eat cookies for breakfast. It might sound great, but eating something when you have absolutely no desire to eat things is... quite unpleasant, really.
---
layout: blog
title: 😢 Lows
description: About depression and what it feels like when I don't know what to do with myself
tags: [Miscellaneous]
discuss: https://twitter.com/Ellpeck/status/1186028260838334471
archived: true
---
So some of you might be disappointed that this *isn't* the next Java tutorial. Well, today and the last couple of days, I haven't been feeling like working on those, because.. I've been depressed again. I never know how to describe it, or how to say it without seeming like it's just.. something I say when I'm too lazy to work on things.
This post will probably be a little all over the place, because that's also the mood that my head is currently in, but I'll try to explain to you what it feels like to me to have a low low follow a high high, mood-wise.
# Highs
When I say that I'm depressed, it means something different than when I say that I'm suffering from depression. Because, for me, depression isn't a constant state. Before depression hits, I have long periods of highs, long periods of what non-depressed people would probably just call.. normal moods.
I get up in the mornings, I enjoy breakfast, I go to University, I do my daily chores, I sit down at my computer, I code, I play some games, and then I go to bed. Normal days, really.
And if you don't suffer from depression, then you might think "Well, what's so special about that?", and the annoying thing is: That's *exactly* what I also think when I'm experiencing a high. I take it for granted, and I never even stop to think about how great it is that I'm currently feeling normal about my life.
# Lows
And then a low hits. And the longer the high has been going on for, the longer I was happy for, the harder it hits. Right now, I've been feeling low for two days, after a high that lasted almost *a month*. When I'm going through a low phase like now, my day works a lot differently.
I wake up at noon. I stay in bed. I check my phone. Hours pass. I think to myself "What am I doing?" I check my phone. After a while, I force myself to get up; I might even take a shower. I find some cookies in the back of my fridge; those'll be good enough for breakfast, I think to myself. I sit down at my computer. I watch some videos. "What am I doing?" Maybe some more videos. "What am I doing? I don't know what to do." I start Minecraft. "This sounds like fun!" I play for five minutes. I close the game again. "What am I doing?" I watch some videos. I start The Sims 4. I play for half an hour, and then I close the game again. "What am I doing?" I watch some more YouTube videos. I listen to some music. Should I do my coursework now? Not in the mood. I open IntelliJ. Let's work on some code. I glance at a couple of files, and then I close it again. "What am I doing?"
Take that whole description, and slow it down by about 200%. That's what a low day feels like to me. I feel like every single hour; every single *minute* even, is filled with doubt and emptiness and just... nothing.
This is so hard to explain for me because I also don't.. see myself in these low phases. I don't look at myself. I don't observe what I'm doing. It's like I'm not even there; it's like I'm not inside my own body, doing these activities with it. It's like it's just doing everything on some sort of weird auto-pilot mode.
So until that passes, I probably won't be working on any tutorials, or anything, really. Let's hope it passes fairly soon, because I'm honestly sick of forcing myself to eat cookies for breakfast. It might sound great, but eating something when you have absolutely no desire to eat things is... quite unpleasant, really.

View file

@ -1,151 +1,158 @@
For this tutorial, let's expand on the car dealership example from the end of the last tutorial: You have a car dealership, and you want to expand it to sell various other types of motor vehicles; let's say... motorbikes and trucks as well. Obviously, these different kinds of vehicles all have different properties: A motorbike requires the use of a helmet, and an important piece of information about a truck might be how big its storage area is.
Now, this is where object-oriented languages like Java shine.
# Extending Other Classes
The concept of extending other classes is probably best explained with an example, so to start out with, let's create a class that represents a car with a certain amount of wheels:
```java
public class Car {
public int amountOfWheels;
public Car(int amountOfWheels) {
this.amountOfWheels = amountOfWheels;
}
}
```
As you can see, this is just a simple class with a variable that can store how many wheels a vehicle has.
Now, let's imagine we want to create a class that represents a truck, and we would want any truck to store information on how many wheels it has, as well as how many cubic meters of storage it has available:
```java
public class Truck {
public int amountOfWheels;
public int storageArea;
public Truck(int amountOfWheels, int storageArea) {
this.amountOfWheels = amountOfWheels;
this.storageArea = storageArea;
}
}
```
As you can see, just creating the class like usual isn't really that pretty, because now both our `Car` class and our `Truck` class have separate `amountOfWheels` fields, despite the fact that they really refer to the same thing: The amount of wheels of a vehicle.
What we'd like to be able to do instead is to have the amount of wheels stored in a different location that both our `Truck` and our `Car` class can access. The first step of achieving this is creating another class that only stores information both of our vehicles should have:
```java
public class Vehicle {
public int amountOfWheels;
public Vehicle(int amountOfWheels) {
this.amountOfWheels = amountOfWheels;
}
}
```
Now, we can use the `extends` keyword that Java provides to cause both of our other classes to also have access to this information. Let's modify our `Car` class first and I'll explain afterwards what exactly our modification will do.
```java
public class Car extends Vehicle {
public Car(int amountOfWheels) {
// Note that the line below will be explained in a bit
super(amountOfWheels);
}
}
```
As you can see, our `Car` class now `extends Vehicle`. This means that the `Car` class is basically just an *upgraded* version of the `Vehicle` class: It contains all of the information that the `Vehicle` class provides (including fields and methods), but it can also provide its own additional information. From the perspective of `Car`, `Vehicle` is the *superclass*, and from the perspective of `Vehicle`, `Car` is a *child class*.
It should be noted that any class can only extend *one* other class, so `Car` couldn't extend both `Vehicle` and, let's say, `FueledDevice`. However, the other way around is possible: `Vehicle` can be extended by multiple classes, meaning that we can modify our `Truck` class to also be a child class like this:
```java
public class Truck extends Vehicle {
public int storageArea;
public Truck(int amountOfWheels, int storageArea) {
super(amountOfWheels);
this.storageArea = storageArea;
}
}
```
As you can see, the `Truck` class now uses the `amountOfWheels` field from its superclass, but it defines its own `storageArea` field that only trucks have.
## `super`
Now, you've probably been wondering what line 5 means: `super(amountOfWheels)`. As we defined earlier, our `Vehicle` class takes one argument in its constructor: The amount of wheels that it has. As we also previously discussed, a constructor *has to be called* when a new object is created. So, when creating a `Truck` object, the `Truck` constructor is called, but, in turn, it needs to also implicitly call the `Vehicle` constructor.
When placing a `super()` call at the start of the constructor of a class, the constructor of the class that is being extended will be called. If that class takes a set of arguments in its constructor, we have to pass it those arguments, similarly to if we were creating an object of that class.
That's what our `super(amountOfWheels)` is doing here: It's calling the constructor of `Vehicle` with the `amountOfWheels` argument.
It should be noted that we don't *necessarily* have to pass a variable into the super constructor. For instance, we know for a fact that a truck will always have four wheels, so we can modify our `Truck` constructor to always specify 4 as the wheel amount given to the superclass:
```java
public Truck(int storageArea) {
super(4);
this.storageArea = storageArea;
}
```
## The `Object` class
It should be noted at this point that *all classes* extend the `Object` class implicitly (meaning you never have to write `extends Object` anywhere and it will still hold true). This will be useful pretty soon.
## About Variable Types
Let's imagine we want to store the stock that we sell at our dealership, without specifying separate lists for the different kinds of vehicles we're selling. What we can do is have that variable be of type `Vehicle`, which will automatically allow instances of child classes to be stored in the variable (or list) like so:
```java
ArrayList<Vehicle> stock = new ArrayList<>();
stock.add(new Car(4));
stock.add(new Truck(10));
// or in the case of variables
Vehicle car = new Car(4);
Vehicle truck = new Truck(10);
```
# `instanceof` and Type Casting
Staying with our list of the stock we have, let's imagine we want to find all of the *trucks* that we have in stock, disregarding any other kinds of vehicles that we might have.
This is what the `instanceof` keyword can be used for: Using it, you can ask any object if it derives of a certain class; that is, if any object is an instance of the specified class.
Let's create a method to return a list of all of the cars we have in stock:
```java
import java.util.ArrayList;
public class Main {
private static ArrayList<Vehicle> stock = new ArrayList<>();
public static void main(String[] args) {
stock.add(new Car(4));
stock.add(new Truck(10));
getTrucks();
}
private static void getTrucks() {
for (int i = 0; i < stock.size(); i++) {
Vehicle vehicle = stock.get(i);
if (vehicle instanceof Truck) {
System.out.println("Vehicle " + i + " is a truck!");
}
}
}
}
```
As you can see, I've expanded the code from before to have a `getTrucks` method, which iterates through every vehicle we have in stock and if it finds a vehicle that is an instance of the `Truck` class, it will print out a message.
Another way to think about the `instanceof` keyword is as a weird, clunky synonym to the word "is": When asking `vehicle instanceof Truck`, we're really asking "Is this vehicle a truck?"
Now that we know which of the vehicles we have in stock are trucks, it might also be useful to know the storage capabilities of all of those trucks. However, as you can see, the `i`th vehicle we're currently inspecting is always of typ `Vehicle`, as that's what we store in the `stock` list. That means that we won't be able to access any `Truck`'s `storageArea` variable. That's what the concept of *type casting* can be used for.
If you have a variable of a certain type, but you know for a fact that it's actually an instance of a *child class* of that type, then you can change that variable to be of the child class' type instead like so:
```java
Vehicle vehicle = stock.get(i);
if (vehicle instanceof Truck) {
Truck truck = (Truck) vehicle;
System.out.println("The " + i + "th truck can store " + truck.storageArea);
}
```
As you can see, to type cast a variable into a different type, all you have to do is write that type in parentheses `()` in front of the variable.[^1] Cool.
# Conclusion
So today, you learned one of the important aspects of object oriented programming, which is very useful in a heap of different scenarios, as you will be able to see throughout the next tutorials. Next time, we'll be covering overriding methods from superclasses, which will allow you to do even more cool stuff with inheritance.[^2]
I'm sorry that I've been taking such a long break from the tutorials, but after asking on Twitter if you people actually learn from them, a lot of you said that you do, and so that gave me the motivation to continue working on these.
So thanks a lot, and happy coding! <3
***
[^1]: As you might be able to see now, this concept is called type *casting* because it's like we're putting the vehicle into a mold (or a *cast*) to reshape it into a different type.
[^2]: I was originally planning on including that in this tutorial already, but I noticed that that might be a bit too much after all, so I'll be doing it next time instead.
---
layout: blog
title: "☕ Java Tutorial, Part 6: Inheritance"
description: In this Java tutorial for beginners, we cover classes extending other classes and the instanceof keyword.
tags: [Programming]
discuss: https://twitter.com/Ellpeck/status/1189904487722487809
archived: true
---
For this tutorial, let's expand on the car dealership example from the end of the last tutorial: You have a car dealership, and you want to expand it to sell various other types of motor vehicles; let's say... motorbikes and trucks as well. Obviously, these different kinds of vehicles all have different properties: A motorbike requires the use of a helmet, and an important piece of information about a truck might be how big its storage area is.
Now, this is where object-oriented languages like Java shine.
# Extending Other Classes
The concept of extending other classes is probably best explained with an example, so to start out with, let's create a class that represents a car with a certain amount of wheels:
```java
public class Car {
public int amountOfWheels;
public Car(int amountOfWheels) {
this.amountOfWheels = amountOfWheels;
}
}
```
As you can see, this is just a simple class with a variable that can store how many wheels a vehicle has.
Now, let's imagine we want to create a class that represents a truck, and we would want any truck to store information on how many wheels it has, as well as how many cubic meters of storage it has available:
```java
public class Truck {
public int amountOfWheels;
public int storageArea;
public Truck(int amountOfWheels, int storageArea) {
this.amountOfWheels = amountOfWheels;
this.storageArea = storageArea;
}
}
```
As you can see, just creating the class like usual isn't really that pretty, because now both our `Car` class and our `Truck` class have separate `amountOfWheels` fields, despite the fact that they really refer to the same thing: The amount of wheels of a vehicle.
What we'd like to be able to do instead is to have the amount of wheels stored in a different location that both our `Truck` and our `Car` class can access. The first step of achieving this is creating another class that only stores information both of our vehicles should have:
```java
public class Vehicle {
public int amountOfWheels;
public Vehicle(int amountOfWheels) {
this.amountOfWheels = amountOfWheels;
}
}
```
Now, we can use the `extends` keyword that Java provides to cause both of our other classes to also have access to this information. Let's modify our `Car` class first and I'll explain afterwards what exactly our modification will do.
```java
public class Car extends Vehicle {
public Car(int amountOfWheels) {
// Note that the line below will be explained in a bit
super(amountOfWheels);
}
}
```
As you can see, our `Car` class now `extends Vehicle`. This means that the `Car` class is basically just an *upgraded* version of the `Vehicle` class: It contains all of the information that the `Vehicle` class provides (including fields and methods), but it can also provide its own additional information. From the perspective of `Car`, `Vehicle` is the *superclass*, and from the perspective of `Vehicle`, `Car` is a *child class*.
It should be noted that any class can only extend *one* other class, so `Car` couldn't extend both `Vehicle` and, let's say, `FueledDevice`. However, the other way around is possible: `Vehicle` can be extended by multiple classes, meaning that we can modify our `Truck` class to also be a child class like this:
```java
public class Truck extends Vehicle {
public int storageArea;
public Truck(int amountOfWheels, int storageArea) {
super(amountOfWheels);
this.storageArea = storageArea;
}
}
```
As you can see, the `Truck` class now uses the `amountOfWheels` field from its superclass, but it defines its own `storageArea` field that only trucks have.
## `super`
Now, you've probably been wondering what line 5 means: `super(amountOfWheels)`. As we defined earlier, our `Vehicle` class takes one argument in its constructor: The amount of wheels that it has. As we also previously discussed, a constructor *has to be called* when a new object is created. So, when creating a `Truck` object, the `Truck` constructor is called, but, in turn, it needs to also implicitly call the `Vehicle` constructor.
When placing a `super()` call at the start of the constructor of a class, the constructor of the class that is being extended will be called. If that class takes a set of arguments in its constructor, we have to pass it those arguments, similarly to if we were creating an object of that class.
That's what our `super(amountOfWheels)` is doing here: It's calling the constructor of `Vehicle` with the `amountOfWheels` argument.
It should be noted that we don't *necessarily* have to pass a variable into the super constructor. For instance, we know for a fact that a truck will always have four wheels, so we can modify our `Truck` constructor to always specify 4 as the wheel amount given to the superclass:
```java
public Truck(int storageArea) {
super(4);
this.storageArea = storageArea;
}
```
## The `Object` class
It should be noted at this point that *all classes* extend the `Object` class implicitly (meaning you never have to write `extends Object` anywhere and it will still hold true). This will be useful pretty soon.
## About Variable Types
Let's imagine we want to store the stock that we sell at our dealership, without specifying separate lists for the different kinds of vehicles we're selling. What we can do is have that variable be of type `Vehicle`, which will automatically allow instances of child classes to be stored in the variable (or list) like so:
```java
ArrayList<Vehicle> stock = new ArrayList<>();
stock.add(new Car(4));
stock.add(new Truck(10));
// or in the case of variables
Vehicle car = new Car(4);
Vehicle truck = new Truck(10);
```
# `instanceof` and Type Casting
Staying with our list of the stock we have, let's imagine we want to find all of the *trucks* that we have in stock, disregarding any other kinds of vehicles that we might have.
This is what the `instanceof` keyword can be used for: Using it, you can ask any object if it derives of a certain class; that is, if any object is an instance of the specified class.
Let's create a method to return a list of all of the cars we have in stock:
```java
import java.util.ArrayList;
public class Main {
private static ArrayList<Vehicle> stock = new ArrayList<>();
public static void main(String[] args) {
stock.add(new Car(4));
stock.add(new Truck(10));
getTrucks();
}
private static void getTrucks() {
for (int i = 0; i < stock.size(); i++) {
Vehicle vehicle = stock.get(i);
if (vehicle instanceof Truck) {
System.out.println("Vehicle " + i + " is a truck!");
}
}
}
}
```
As you can see, I've expanded the code from before to have a `getTrucks` method, which iterates through every vehicle we have in stock and if it finds a vehicle that is an instance of the `Truck` class, it will print out a message.
Another way to think about the `instanceof` keyword is as a weird, clunky synonym to the word "is": When asking `vehicle instanceof Truck`, we're really asking "Is this vehicle a truck?"
Now that we know which of the vehicles we have in stock are trucks, it might also be useful to know the storage capabilities of all of those trucks. However, as you can see, the `i`th vehicle we're currently inspecting is always of typ `Vehicle`, as that's what we store in the `stock` list. That means that we won't be able to access any `Truck`'s `storageArea` variable. That's what the concept of *type casting* can be used for.
If you have a variable of a certain type, but you know for a fact that it's actually an instance of a *child class* of that type, then you can change that variable to be of the child class' type instead like so:
```java
Vehicle vehicle = stock.get(i);
if (vehicle instanceof Truck) {
Truck truck = (Truck) vehicle;
System.out.println("The " + i + "th truck can store " + truck.storageArea);
}
```
As you can see, to type cast a variable into a different type, all you have to do is write that type in parentheses `()` in front of the variable.[^1] Cool.
# Conclusion
So today, you learned one of the important aspects of object oriented programming, which is very useful in a heap of different scenarios, as you will be able to see throughout the next tutorials. Next time, we'll be covering overriding methods from superclasses, which will allow you to do even more cool stuff with inheritance.[^2]
I'm sorry that I've been taking such a long break from the tutorials, but after asking on Twitter if you people actually learn from them, a lot of you said that you do, and so that gave me the motivation to continue working on these.
So thanks a lot, and happy coding! <3
[^1]: As you might be able to see now, this concept is called type *casting* because it's like we're putting the vehicle into a mold (or a *cast*) to reshape it into a different type.
[^2]: I was originally planning on including that in this tutorial already, but I noticed that that might be a bit too much after all, so I'll be doing it next time instead.

View file

@ -1,227 +1,234 @@
So it's been a hot minute since the last tutorial, and I apologize for that. However, it seems like there are some people that actually use these tutorials to properly learn Java, and so I didn't want to leave you all hanging.
Today's tutorial is going to cover method overrides, which are another awesome object orientation concept that will help you out greatly when programming.
Let's imagine that we want our different vehicle types (`Car` and `Truck`) from the last tutorial to be able to print out some information about themselves to the console. To do so, we could simply add a `printInformation()` method to each of the classes. However, that would end up being problematic if we wanted to print out information about *the entirety of our stock*, which, as you might recall, is stored in our `ArrayList<Vehicle> stock`, as we would have to create `instanceof` checks for both `Car` and `Truck` to be able to access their `printInformation()` methods.
An easy fix for that would be to create a basic `printInformation()` method in our base class (`Vehicle`) and then *override* that method in our subclasses, allowing us to change their behavior. Let's see what that would look like:
```java
// Vehicle.java
public class Vehicle {
public int amountOfWheels;
public Vehicle(int amountOfWheels) {
this.amountOfWheels = amountOfWheels;
}
public void printInformation() {
// Does nothing for now
}
}
// Car.java
public class Car extends Vehicle {
public Car(int amountOfWheels) {
super(amountOfWheels);
}
@Override
public void printInformation() {
System.out.println("This car has " + this.amountOfWheels + " wheels");
}
}
```
As you can see, I've added a simple `printInformation()` method to the `Vehicle` class (which does absolutely nothing for now). However, I have then *overridden* that method in the `Car` class. The way I've done that is by adding a method that has the exact same name, return type and accepted parameters as the base class's method. Technically, that would already be enough to override a method, however, to make it a little clearer to read, people usually like to add the `@Override` annotation[^1] above the method.
So what does this mean, exactly? Basically, when calling the `printInformation()` method on an instance of the `Vehicle` class, nothing will happen, because the `Vehicle`'s method is empty. However, when calling the method on an instance of the `Car` class, the print statement above will be executed, printing information about the car's wheel amount.
The important thing to understand is this: Which class's method is called isn't determined by the *variable type*, but by the type that the object itself has. Let's check out this example:
```java
// This is in our Main class from last time
private static void getTrucks() {
for (int i = 0; i < stock.size(); i++) {
Vehicle vehicle = stock.get(i);
vehicle.printInformation();
}
}
```
Now, each vehicle that isn't a car (so each truck in ou example) will not print out any information, but each of our cars will print out the information we specified above, despite the fact that we're not doing any `instanceof` checks or anything else. Cool.
# Calling `super` methods
Now, if we wanted to also add an override of the `printInformation()` method to our `Truck` class, we will come across a minor annoyance: To print out the truck's amount of wheels, we'd basically have to copy the print statement from our `Car` class, which is a bit ugly.
Well, that's where `super` calls come to the rescue! We've already briefly touched on super calls when talking about the super constructor for extending other classes, and super calls are very similar to that. Let's change our code up a bit, and then I'll explain what it all means.
```java
// Vehicle.java
public class Vehicle {
public int amountOfWheels;
public Vehicle(int amountOfWheels) {
this.amountOfWheels = amountOfWheels;
}
public void printInformation() {
System.out.println("Vehicle info:");
System.out.println("Wheel amount: " + this.amountOfWheels);
}
}
// Car.java
public class Car extends Vehicle {
public Car(int amountOfWheels) {
super(amountOfWheels);
}
@Override
public void printInformation() {
super.printInformation();
}
}
```
As you can see, I've modified the `Vehicle`'s method to print out the wheel amount by default, and I've changed the `Car`'s method to call `super.printInformation()`. What this call does is simply execute its parent class's `printInformation()` method, so a car will also have its wheel amount printed out just the same as any other vehicle.
It should be noted that simply calling the `super` method in an override and doing absolutely nothing else is *the default behavior*, meaning that, in the example above, we could leave out the `printInformation()` override in our `Car` class completely and still get the same effect.
Now let's add a `printInformation()` override to our `Truck` class as well, but this time, let's extend the behavior a bit:
```java
public class Truck extends Vehicle {
public int storageArea;
public Truck(int storageArea) {
super(4);
this.storageArea = storageArea;
}
@Override
public void printInformation() {
super.printInformation();
System.out.println("Storage area: " + this.storageArea);
}
}
```
Now, calling a truck's `printInformation()` method will *first* print out "Vehicle information:", then the wheel amount, and then the storage area. If we wanted this behavior to occur in a different order, we could simply swap the two lines as follows:
```java
@Override
public void printInformation() {
System.out.println("Storage area: " + this.storageArea);
super.printInformation();
}
```
Now, the storage area will be printed out *first*, followed by "Vehicle information:" and the wheel amount.[^2]
# `Object` methods
As I briefly mentioned in the last tutorial, *all* classes implicitly extend Java's `Object` class without you having to specify that information. This is finally going to be useful now, as this class provides some useful methods that we can override in our implementations.
## `toString()`
This method is probably the most versatile one of the bunch: It gets called whenever an object's information needs to be converted into a string in some circumstance. For example, if you simply write
```java
Car car = new Car(4);
System.out.println(car);
```
then the car's `toString()` method will be called inside of `println` in order to convert the car's information into a string.
However, by default, the `toString()` method simply prints out some not-so-useful information about the object's internal identifier. If we override this method, however, we can make it display some more useful information. Let's do so in our `Vehicle` and `Car` class as an example:
```java
// Vehicle.java
public class Vehicle {
public int amountOfWheels;
public Vehicle(int amountOfWheels) {
this.amountOfWheels = amountOfWheels;
}
@Override
public String toString() {
return "Vehicle with " + this.amountOfWheels + " wheels";
}
}
// Truck.java
public class Truck extends Vehicle {
public int storageArea;
public Truck(int storageArea) {
super(4);
this.storageArea = storageArea;
}
@Override
public String toString() {
String vehicleInfo = super.toString();
return vehicleInfo + " and " + this.storageArea + " storage area";
}
}
```
As you can see, `Truck` additionally calls the `toString()` super method and then appends some more information to the string created in `Vehicle`. Pretty nice.
## `equals()`
Remember how I told you that you should always use `equals()` to compare two strings instead of the double equals sign `==`?
Well, here's why: Java's `String` class overrides the `equals()` method from `Object` and changes its behavior so that two strings are considered equal if their contents are identical.
As an example, let's first override the `equals()` method in our `Vehicle` class with the default behavior that it would have if you didn't override it at all:
```java
@Override
public boolean equals(Object other) {
return this == other;
}
```
As you can see, the default behavior is simply the double equals sign `==`, which compares if two variable pointers point to the exact same object, as previously explained. That's not what we might want in our example, though.
Let's expand our `Vehicle` class to also have a unique identifier: The license plate's text. Let's say that we want to identify each vehicle by its plate, and so we make it the key factor in determining whether two vehicles are the same or not:
```java
public class Vehicle {
public int amountOfWheels;
public String licensePlate;
public Vehicle(int amountOfWheels, String licensePlate) {
this.amountOfWheels = amountOfWheels;
this.licensePlate = licensePlate;
}
@Override
public boolean equals(Object other) {
if (other instanceof Vehicle) {
Vehicle v = (Vehicle) other;
return v.licensePlate.equals(this.licensePlate);
}
return false;
}
}
```
As you can see, each `Vehicle` now accepts a license plate in the constructor, and the `equals()` method is overridden in a way that makes two vehicles be considered equal if their license plates match exactly.
Now, a great example of how this could be useful is with lists. The `ArrayList` class contains the `contains()` method, which determines if a certain element is already present in the list. Now, the cool thing is that this method uses the `equals()` method on each element to determine whether or not an element is present. We can use this behavior to check if a vehicle with a certain license plate is already in our stock pretty easily:
```java
import java.util.ArrayList;
public class Main {
private static ArrayList<Vehicle> stock = new ArrayList<>();
public static void main(String[] args) {
stock.add(new Car(4, "AC JS 1999"));
stock.add(new Truck(10, "AC NS 1998"));
Car carToCheck = new Car(4, "AC HI 1234");
if (stock.contains(carToCheck)) {
System.out.println("The queried car is already in our stock :)");
}
}
}
```
Obviously, in this specific example, the text won't be printed because there isn't a car with that license plate in the `stock` list.
# Conclusion
So yea, today you learned about the second key concept of object orientation in Java. To put this knowledge to the test, I'm going to give you an exercise all about extending classes and overriding methods that you can try to solve if you want.
> Let's imagine you're trying to create a program that allows you to draw manage different shapes, namely rectangles, right angle triangles and circles. Obviously, these shapes all have different properties, like the rectangle's and triangle's side lengths and the circle's radius. All of the shapes you currently have are stored in a list. For each of the shapes, you want to be able to calculate its circumference as well as its area. To test this program, you add a couple of shapes to your list and calculate the average of all of their areas and circumferences.
Note that you can find some methods and constants you might need in Java's default `Math` class, namely `Math.PI` and `Math.sqrt()`, the latter of which is used to calculate a square root. If you're stuck, you can [check my solution](https://gist.github.com/Ellpeck/8cf63c747313e070b7475d99e2bed5a1).
As always, happy coding!
***
[^1]: Annotations are another rather advanced topic that almost never comes up when using Java. They can be useful sometimes, but you'll hardly ever have a use for them.
[^2]: Which, in this case, would obviously be a bit of a nonsensical order to display this information in. But you get the idea.
---
layout: blog
title: "☕ Java Tutorial, Part 7: Overriding Methods"
description: In this Java tutorial for beginners, we cover overriding methods, calling superclass methods and toString().
tags: [Programming]
discuss: https://twitter.com/Ellpeck/status/1199339701640945664
archived: true
---
So it's been a hot minute since the last tutorial, and I apologize for that. However, it seems like there are some people that actually use these tutorials to properly learn Java, and so I didn't want to leave you all hanging.
Today's tutorial is going to cover method overrides, which are another awesome object orientation concept that will help you out greatly when programming.
Let's imagine that we want our different vehicle types (`Car` and `Truck`) from the last tutorial to be able to print out some information about themselves to the console. To do so, we could simply add a `printInformation()` method to each of the classes. However, that would end up being problematic if we wanted to print out information about *the entirety of our stock*, which, as you might recall, is stored in our `ArrayList<Vehicle> stock`, as we would have to create `instanceof` checks for both `Car` and `Truck` to be able to access their `printInformation()` methods.
An easy fix for that would be to create a basic `printInformation()` method in our base class (`Vehicle`) and then *override* that method in our subclasses, allowing us to change their behavior. Let's see what that would look like:
```java
// Vehicle.java
public class Vehicle {
public int amountOfWheels;
public Vehicle(int amountOfWheels) {
this.amountOfWheels = amountOfWheels;
}
public void printInformation() {
// Does nothing for now
}
}
// Car.java
public class Car extends Vehicle {
public Car(int amountOfWheels) {
super(amountOfWheels);
}
@Override
public void printInformation() {
System.out.println("This car has " + this.amountOfWheels + " wheels");
}
}
```
As you can see, I've added a simple `printInformation()` method to the `Vehicle` class (which does absolutely nothing for now). However, I have then *overridden* that method in the `Car` class. The way I've done that is by adding a method that has the exact same name, return type and accepted parameters as the base class's method. Technically, that would already be enough to override a method, however, to make it a little clearer to read, people usually like to add the `@Override` annotation[^1] above the method.
So what does this mean, exactly? Basically, when calling the `printInformation()` method on an instance of the `Vehicle` class, nothing will happen, because the `Vehicle`'s method is empty. However, when calling the method on an instance of the `Car` class, the print statement above will be executed, printing information about the car's wheel amount.
The important thing to understand is this: Which class's method is called isn't determined by the *variable type*, but by the type that the object itself has. Let's check out this example:
```java
// This is in our Main class from last time
private static void getTrucks() {
for (int i = 0; i < stock.size(); i++) {
Vehicle vehicle = stock.get(i);
vehicle.printInformation();
}
}
```
Now, each vehicle that isn't a car (so each truck in ou example) will not print out any information, but each of our cars will print out the information we specified above, despite the fact that we're not doing any `instanceof` checks or anything else. Cool.
# Calling `super` methods
Now, if we wanted to also add an override of the `printInformation()` method to our `Truck` class, we will come across a minor annoyance: To print out the truck's amount of wheels, we'd basically have to copy the print statement from our `Car` class, which is a bit ugly.
Well, that's where `super` calls come to the rescue! We've already briefly touched on super calls when talking about the super constructor for extending other classes, and super calls are very similar to that. Let's change our code up a bit, and then I'll explain what it all means.
```java
// Vehicle.java
public class Vehicle {
public int amountOfWheels;
public Vehicle(int amountOfWheels) {
this.amountOfWheels = amountOfWheels;
}
public void printInformation() {
System.out.println("Vehicle info:");
System.out.println("Wheel amount: " + this.amountOfWheels);
}
}
// Car.java
public class Car extends Vehicle {
public Car(int amountOfWheels) {
super(amountOfWheels);
}
@Override
public void printInformation() {
super.printInformation();
}
}
```
As you can see, I've modified the `Vehicle`'s method to print out the wheel amount by default, and I've changed the `Car`'s method to call `super.printInformation()`. What this call does is simply execute its parent class's `printInformation()` method, so a car will also have its wheel amount printed out just the same as any other vehicle.
It should be noted that simply calling the `super` method in an override and doing absolutely nothing else is *the default behavior*, meaning that, in the example above, we could leave out the `printInformation()` override in our `Car` class completely and still get the same effect.
Now let's add a `printInformation()` override to our `Truck` class as well, but this time, let's extend the behavior a bit:
```java
public class Truck extends Vehicle {
public int storageArea;
public Truck(int storageArea) {
super(4);
this.storageArea = storageArea;
}
@Override
public void printInformation() {
super.printInformation();
System.out.println("Storage area: " + this.storageArea);
}
}
```
Now, calling a truck's `printInformation()` method will *first* print out "Vehicle information:", then the wheel amount, and then the storage area. If we wanted this behavior to occur in a different order, we could simply swap the two lines as follows:
```java
@Override
public void printInformation() {
System.out.println("Storage area: " + this.storageArea);
super.printInformation();
}
```
Now, the storage area will be printed out *first*, followed by "Vehicle information:" and the wheel amount.[^2]
# `Object` methods
As I briefly mentioned in the last tutorial, *all* classes implicitly extend Java's `Object` class without you having to specify that information. This is finally going to be useful now, as this class provides some useful methods that we can override in our implementations.
## `toString()`
This method is probably the most versatile one of the bunch: It gets called whenever an object's information needs to be converted into a string in some circumstance. For example, if you simply write
```java
Car car = new Car(4);
System.out.println(car);
```
then the car's `toString()` method will be called inside of `println` in order to convert the car's information into a string.
However, by default, the `toString()` method simply prints out some not-so-useful information about the object's internal identifier. If we override this method, however, we can make it display some more useful information. Let's do so in our `Vehicle` and `Car` class as an example:
```java
// Vehicle.java
public class Vehicle {
public int amountOfWheels;
public Vehicle(int amountOfWheels) {
this.amountOfWheels = amountOfWheels;
}
@Override
public String toString() {
return "Vehicle with " + this.amountOfWheels + " wheels";
}
}
// Truck.java
public class Truck extends Vehicle {
public int storageArea;
public Truck(int storageArea) {
super(4);
this.storageArea = storageArea;
}
@Override
public String toString() {
String vehicleInfo = super.toString();
return vehicleInfo + " and " + this.storageArea + " storage area";
}
}
```
As you can see, `Truck` additionally calls the `toString()` super method and then appends some more information to the string created in `Vehicle`. Pretty nice.
## `equals()`
Remember how I told you that you should always use `equals()` to compare two strings instead of the double equals sign `==`?
Well, here's why: Java's `String` class overrides the `equals()` method from `Object` and changes its behavior so that two strings are considered equal if their contents are identical.
As an example, let's first override the `equals()` method in our `Vehicle` class with the default behavior that it would have if you didn't override it at all:
```java
@Override
public boolean equals(Object other) {
return this == other;
}
```
As you can see, the default behavior is simply the double equals sign `==`, which compares if two variable pointers point to the exact same object, as previously explained. That's not what we might want in our example, though.
Let's expand our `Vehicle` class to also have a unique identifier: The license plate's text. Let's say that we want to identify each vehicle by its plate, and so we make it the key factor in determining whether two vehicles are the same or not:
```java
public class Vehicle {
public int amountOfWheels;
public String licensePlate;
public Vehicle(int amountOfWheels, String licensePlate) {
this.amountOfWheels = amountOfWheels;
this.licensePlate = licensePlate;
}
@Override
public boolean equals(Object other) {
if (other instanceof Vehicle) {
Vehicle v = (Vehicle) other;
return v.licensePlate.equals(this.licensePlate);
}
return false;
}
}
```
As you can see, each `Vehicle` now accepts a license plate in the constructor, and the `equals()` method is overridden in a way that makes two vehicles be considered equal if their license plates match exactly.
Now, a great example of how this could be useful is with lists. The `ArrayList` class contains the `contains()` method, which determines if a certain element is already present in the list. Now, the cool thing is that this method uses the `equals()` method on each element to determine whether or not an element is present. We can use this behavior to check if a vehicle with a certain license plate is already in our stock pretty easily:
```java
import java.util.ArrayList;
public class Main {
private static ArrayList<Vehicle> stock = new ArrayList<>();
public static void main(String[] args) {
stock.add(new Car(4, "AC JS 1999"));
stock.add(new Truck(10, "AC NS 1998"));
Car carToCheck = new Car(4, "AC HI 1234");
if (stock.contains(carToCheck)) {
System.out.println("The queried car is already in our stock :)");
}
}
}
```
Obviously, in this specific example, the text won't be printed because there isn't a car with that license plate in the `stock` list.
# Conclusion
So yea, today you learned about the second key concept of object orientation in Java. To put this knowledge to the test, I'm going to give you an exercise all about extending classes and overriding methods that you can try to solve if you want.
> Let's imagine you're trying to create a program that allows you to draw manage different shapes, namely rectangles, right angle triangles and circles. Obviously, these shapes all have different properties, like the rectangle's and triangle's side lengths and the circle's radius. All of the shapes you currently have are stored in a list. For each of the shapes, you want to be able to calculate its circumference as well as its area. To test this program, you add a couple of shapes to your list and calculate the average of all of their areas and circumferences.
Note that you can find some methods and constants you might need in Java's default `Math` class, namely `Math.PI` and `Math.sqrt()`, the latter of which is used to calculate a square root. If you're stuck, you can [check my solution](https://gist.github.com/Ellpeck/8cf63c747313e070b7475d99e2bed5a1).
As always, happy coding!
[^1]: Annotations are another rather advanced topic that almost never comes up when using Java. They can be useful sometimes, but you'll hardly ever have a use for them.
[^2]: Which, in this case, would obviously be a bit of a nonsensical order to display this information in. But you get the idea.

View file

@ -1,92 +1,100 @@
On March 7 of this year, my overwhelmingly popular Minecraft mod Actually Additions [celebrated its fifth birthday](https://www.curseforge.com/minecraft/mc-mods/actually-additions/files/2229705). I personally made my last real contribution to the project [around mid-June 2017](https://github.com/Ellpeck/ActuallyAdditions/commits/main?after=896a082d747a3e19755ded1973544d59fa992787+244), so basically three years ago. Every now and again, though (and by that, I mean almost constantly), I still get asked about the project. The main question I get is: "Will you port Actually Additions to Minecraft 1.15?"
Here's the short answer: No, I won't. I haven't worked on the project in *three years*. But this post isn't about that. It's about why *you* probably shouldn't port it either.
I've grown a lot in the last five years. Yes, I've literally grown in the last five years - I'm 20 years old now. But I've also grown as a programmer, and more importantly, as a game designer. In this post, I'm going to reminisce about some features of Actually Additions, tell you why I added them, how I added them and what I'd do differently if I made the mod today. Since I can't be bothered opening a modpack instance, I'm just going to go off of the information from the [online manual](https://www.ellpeck.de/actaddmanual/) for this list.
# "Actually Additions"
When first starting out with the mod, it was called something like "Some Pretty Techy Stuff". I realized that was a *terrible* name choice though, so I changed it to "Some Pretty *Random* Stuff" pretty quickly. That wasn't much better, so after some (evidently not very much) consideration, I apparently settled on *the worst possible mod name*, "Actually Additions". It makes no grammatical sense unless you add a comma, and it's annoying to say for me because, as any non-native English speaker knows, saying "Actually" is very hard.
# Overpowered Items
A lot of items in Actually Additions suffer the exact same problem: They're *balanced because they take effort*. What that means is that they're overpowered beyond belief, because they usually take a somewhat large amount of resources to create (though that isn't even true for some of them) and then do something crazy at (basically) no cost. All of these items are ones I'd either implement completely differently today, or scrap entirely:
- The Fishing Net
- Greenhouse Glass
- The Item Repairer
- The Ring of Growth
## Oh God, Storage Crates
Why do people love them so much? They make every other mod's storage options *completely worthless*. Why they're not disabled in more modpacks is a complete mystery to me.
I don't think there's any code I've written in my life that I regret as much as Storage Crates. God, they're awful.
## Solar Panels
When I first implemented them, I thought passive power generation was perfectly fine. That opinion changed pretty quickly the more I got used to working with RF-based mods and the struggle of trying to balance them against each other. So... I changed solar panels to be ridiculously expensive and only generate 8 RF per tick, which is a rather small amount.
Still, a lot of people kept on using them. *Creative*.
If you really, *really* want to port Actually Additions to 1.15, at least remove all of these unbalanced features first. Or at least try to make them less horrible somehow.
# Phantomfaces
I still, to this day, *adore* the way I implemented Phantomfaces. They look good, they work well, they have unique functionality, but... **everyone uses them wrong**. They're not supposed to be a means of item transportation. How could I make them be more restricted to their intended use case? I don't really know, honestly.
# The Coffee Maker
Etho is my favorite YouTuber of all time, and always has been. When he first used the Coffee Maker in his HermitCraft modded series, I legitimately cried of happiness. Following that point, he made and used it in every modded series that it was available in.
I still think it's a great block with awesomely unique functionality, and I have been considering implementing something similar in functionality into my newer mod Nature's Aura.
# Canola and Oil
There's not much to say about the entire canola power generation system other than... I love it. I think it's a great way to generate power in almost every modpack. It's easy to set up, it scales well to higher tiers of power generation and it's a fun thing to automate. It makes me happy to see that a lot of people seem to still agree nowadays.
# Wings of the Bats
I'm not the *biggest* fan of creative flight in survival mode anymore in general, but I do like how I implemented the Wings of the Bats. For those of you who don't know: They have a charge that runs out when you fly with them. If you land on the ground, the charge slowly fills back up again. *But* if you fly up to the ceiling and hover there for a while, the charge fills up a lot faster than if you were to stand on the ground. Because you're a bat.
# AIOTS
...were a huge mistake. "All-In-One-Tools", originally called something ridiculous like "Paxelordoe" (Pickaxe, Shovel, Sword and Hoe), were pretty much intended to be a better version of Mekanism's paxels. I like the idea of all-in-one tools in general, but I think they should be later game. I also think they should *not* have **eight times** the durability of the tools they were made from.
# Laser Relays
Laser Relays were my sorry attempt at copying Immersive Engineering's awesome wires. Did I succeed in that? No. But did I still make something hella awesome? Yes. As with all of the other things on this list that I actually do like, they scale well and they look good.
I personally especially love the item laser relays, because they can do almost as much as logistics pipes can, minus the request terminal. Do people that use them realize that? Most of the time, no. Is there a way to fix that? Probably, but I don't know what it is.
# The Atomic Reconstructor
...is awesome, plain and simple. This is one of the features I added surprisingly late in development, probably a year or so after the mod originally came out, if I had to guess. The reason I like it so much is that I'm a huge fan of in-world crafting. It's less boring than sitting in front of some UI-heavy machine, and it's more fun to automate too. If I made Actually Additions again today, the Atomic Reconstructor would still be the mod's centerpiece. Also, the lenses are cool.
The crystals, though... I don't think I'd want them to work the same nowadays. The names were ridiculous, the textures were weird and the materials used to make them were generic and boring. Actually, I originally picked the ridiculous anagram-ish names specifically because I *knew* YouTubers would have trouble saying them, and the thought of that was hilarious to me.
# Lush Caves
They don't fit the mod (because it's not a biome/world gen mod), so most modpacks disable them. I don't really understand why, though, because they add so much to the world!
This game desperately needs a cave update.
# The Textures
The textures for Actually Additions were created by three separate artists, as well as *me*, the latter of which is very far from an artist.
That's why the visual style of the mod is inconsistent and honestly pretty gross. The textures of blocks don't fit together, most of them are flat and uninspired. The mod doesn't use that many nice particles and other visual effects to enhance the look and feel of mechanics.
Altogether, when playing the mod now, the texture and overall visual style of the mod make me feel like I'm playing some bizarre combination of old ruins, a tech mod and a confused magic mod.
# Honorable-ish Mentions
**Black Quartz**. Why didn't I make it drop the item directly instead of the ore? I don't know. Weird choice, though.
**Smiley Clouds**. God, they're creepy. Yes, they were just supposed to be a clone of Botania's Tiny Potato. Were they as well designed? No. Were they as cool? No. Would I ever want them in my life ever again? Definitely not.
**Treasure Chests**. I like the feature, but... does anyone even know they exist?
**ESDs** were actually one of the first features in the mod. I still like them, though their UI is clunky and hard to understand. If I made Actually Additions today, I'd definitely include them, but with a different name and a cleaner UI.
**Experience Solidifiers** are great. I like the solidified experience too, though I know that a lot of modpack makers seem to disagree, as it's disabled in almost every pack. I'd still want them today, though.
**The Feeder** is an almost exact copy of the Railcraft Feeding Station thing, because back in the day, I was so unoriginal that I just stole other people's content. Yea.
**Drills**. Love 'em. The upgrade system is neat and scales well, they use a good amount of power and they just look *so cool*.
**Balls of Fur** are actually called "hairy balls." I don't know if the fact that I *didn't* realize that that wasn't the best name when implementing them makes this whole situation less embarrassing or more embarrassing.
**The Empowerer** is still pretty cool in my opinion. I'd probably use it as a crafting mechanic again.
# What Do We Learn From This?
Well, I don't know if you learn anything from this, but there's one thing I learned for sure: I'm a lot better of a programmer and a lot better of a game designer now.
Sure, I'd still consider Actually Additions to be a pretty good mod, and it's definitely a nice project to be known for. But I don't think I'd do almost anything in the mod the same way nowadays.
Also, I kind of think that Actually Additions has run its course in some way; it's lived its life. It's important to remember that there is a huge amount of new, unexplored mod ideas still out there, and that there are already some great mods that implement unique, new ideas. Shouldn't we let those mods step into the spotlight now?
---
layout: blog
title: ⛔ Oh God, Please Don't Port Actually Additions
description: As Actually Additions celebrates its fifth birthday, I break down what I like and dislike about it.
tags: [Minecraft]
discuss: https://twitter.com/Ellpeck/status/1259600490377216002
---
On March 7 of this year, my overwhelmingly popular Minecraft mod Actually Additions [celebrated its fifth birthday](https://www.curseforge.com/minecraft/mc-mods/actually-additions/files/2229705). I personally made my last real contribution to the project [around mid-June 2017](https://github.com/Ellpeck/ActuallyAdditions/commits/main?after=896a082d747a3e19755ded1973544d59fa992787+244), so basically three years ago. Every now and again, though (and by that, I mean almost constantly), I still get asked about the project. The main question I get is: "Will you port Actually Additions to Minecraft 1.15?"
Here's the short answer: No, I won't. I haven't worked on the project in *three years*. But this post isn't about that. It's about why *you* probably shouldn't port it either.
I've grown a lot in the last five years. Yes, I've literally grown in the last five years - I'm 20 years old now. But I've also grown as a programmer, and more importantly, as a game designer. In this post, I'm going to reminisce about some features of Actually Additions, tell you why I added them, how I added them and what I'd do differently if I made the mod today. Since I can't be bothered opening a modpack instance, I'm just going to go off of the information from the [online manual](https://ellpeck.de/actaddmanual/) for this list.
# "Actually Additions"
When first starting out with the mod, it was called something like "Some Pretty Techy Stuff". I realized that was a *terrible* name choice though, so I changed it to "Some Pretty *Random* Stuff" pretty quickly. That wasn't much better, so after some (evidently not very much) consideration, I apparently settled on *the worst possible mod name*, "Actually Additions". It makes no grammatical sense unless you add a comma, and it's annoying to say for me because, as any non-native English speaker knows, saying "Actually" is very hard.
# Overpowered Items
A lot of items in Actually Additions suffer the exact same problem: They're *balanced because they take effort*. What that means is that they're overpowered beyond belief, because they usually take a somewhat large amount of resources to create (though that isn't even true for some of them) and then do something crazy at (basically) no cost. All of these items are ones I'd either implement completely differently today, or scrap entirely:
- The Fishing Net
- Greenhouse Glass
- The Item Repairer
- The Ring of Growth
## Oh God, Storage Crates
Why do people love them so much? They make every other mod's storage options *completely worthless*. Why they're not disabled in more modpacks is a complete mystery to me.
I don't think there's any code I've written in my life that I regret as much as Storage Crates. God, they're awful.
## Solar Panels
When I first implemented them, I thought passive power generation was perfectly fine. That opinion changed pretty quickly the more I got used to working with RF-based mods and the struggle of trying to balance them against each other. So... I changed solar panels to be ridiculously expensive and only generate 8 RF per tick, which is a rather small amount.
Still, a lot of people kept on using them. *Creative*.
If you really, *really* want to port Actually Additions to 1.15, at least remove all of these unbalanced features first. Or at least try to make them less horrible somehow.
# Phantomfaces
I still, to this day, *adore* the way I implemented Phantomfaces. They look good, they work well, they have unique functionality, but... **everyone uses them wrong**. They're not supposed to be a means of item transportation. How could I make them be more restricted to their intended use case? I don't really know, honestly.
# The Coffee Maker
Etho is my favorite YouTuber of all time, and always has been. When he first used the Coffee Maker in his HermitCraft modded series, I legitimately cried of happiness. Following that point, he made and used it in every modded series that it was available in.
I still think it's a great block with awesomely unique functionality, and I have been considering implementing something similar in functionality into my newer mod Nature's Aura.
# Canola and Oil
There's not much to say about the entire canola power generation system other than... I love it. I think it's a great way to generate power in almost every modpack. It's easy to set up, it scales well to higher tiers of power generation and it's a fun thing to automate. It makes me happy to see that a lot of people seem to still agree nowadays.
# Wings of the Bats
I'm not the *biggest* fan of creative flight in survival mode anymore in general, but I do like how I implemented the Wings of the Bats. For those of you who don't know: They have a charge that runs out when you fly with them. If you land on the ground, the charge slowly fills back up again. *But* if you fly up to the ceiling and hover there for a while, the charge fills up a lot faster than if you were to stand on the ground. Because you're a bat.
# AIOTS
...were a huge mistake. "All-In-One-Tools", originally called something ridiculous like "Paxelordoe" (Pickaxe, Shovel, Sword and Hoe), were pretty much intended to be a better version of Mekanism's paxels. I like the idea of all-in-one tools in general, but I think they should be later game. I also think they should *not* have **eight times** the durability of the tools they were made from.
# Laser Relays
Laser Relays were my sorry attempt at copying Immersive Engineering's awesome wires. Did I succeed in that? No. But did I still make something hella awesome? Yes. As with all of the other things on this list that I actually do like, they scale well and they look good.
I personally especially love the item laser relays, because they can do almost as much as logistics pipes can, minus the request terminal. Do people that use them realize that? Most of the time, no. Is there a way to fix that? Probably, but I don't know what it is.
# The Atomic Reconstructor
...is awesome, plain and simple. This is one of the features I added surprisingly late in development, probably a year or so after the mod originally came out, if I had to guess. The reason I like it so much is that I'm a huge fan of in-world crafting. It's less boring than sitting in front of some UI-heavy machine, and it's more fun to automate too. If I made Actually Additions again today, the Atomic Reconstructor would still be the mod's centerpiece. Also, the lenses are cool.
The crystals, though... I don't think I'd want them to work the same nowadays. The names were ridiculous, the textures were weird and the materials used to make them were generic and boring. Actually, I originally picked the ridiculous anagram-ish names specifically because I *knew* YouTubers would have trouble saying them, and the thought of that was hilarious to me.
# Lush Caves
They don't fit the mod (because it's not a biome/world gen mod), so most modpacks disable them. I don't really understand why, though, because they add so much to the world!
This game desperately needs a cave update.
# The Textures
The textures for Actually Additions were created by three separate artists, as well as *me*, the latter of which is very far from an artist.
That's why the visual style of the mod is inconsistent and honestly pretty gross. The textures of blocks don't fit together, most of them are flat and uninspired. The mod doesn't use that many nice particles and other visual effects to enhance the look and feel of mechanics.
Altogether, when playing the mod now, the texture and overall visual style of the mod make me feel like I'm playing some bizarre combination of old ruins, a tech mod and a confused magic mod.
# Honorable-ish Mentions
**Black Quartz**. Why didn't I make it drop the item directly instead of the ore? I don't know. Weird choice, though.
**Smiley Clouds**. God, they're creepy. Yes, they were just supposed to be a clone of Botania's Tiny Potato. Were they as well designed? No. Were they as cool? No. Would I ever want them in my life ever again? Definitely not.
**Treasure Chests**. I like the feature, but... does anyone even know they exist?
**ESDs** were actually one of the first features in the mod. I still like them, though their UI is clunky and hard to understand. If I made Actually Additions today, I'd definitely include them, but with a different name and a cleaner UI.
**Experience Solidifiers** are great. I like the solidified experience too, though I know that a lot of modpack makers seem to disagree, as it's disabled in almost every pack. I'd still want them today, though.
**The Feeder** is an almost exact copy of the Railcraft Feeding Station thing, because back in the day, I was so unoriginal that I just stole other people's content. Yea.
**Drills**. Love 'em. The upgrade system is neat and scales well, they use a good amount of power and they just look *so cool*.
**Balls of Fur** are actually called "hairy balls." I don't know if the fact that I *didn't* realize that that wasn't the best name when implementing them makes this whole situation less embarrassing or more embarrassing.
**The Empowerer** is still pretty cool in my opinion. I'd probably use it as a crafting mechanic again.
# What Do We Learn From This?
Well, I don't know if you learn anything from this, but there's one thing I learned for sure: I'm a lot better of a programmer and a lot better of a game designer now.
Sure, I'd still consider Actually Additions to be a pretty good mod, and it's definitely a nice project to be known for. But I don't think I'd do almost anything in the mod the same way nowadays.
Also, I kind of think that Actually Additions has run its course in some way; it's lived its life. It's important to remember that there is a huge amount of new, unexplored mod ideas still out there, and that there are already some great mods that implement unique, new ideas. Shouldn't we let those mods step into the spotlight now?

View file

@ -1,15 +1,22 @@
Over the years, my views on social media and its purpose have changed many times. When I first started out using the internet, playing Minecraft and watching YouTube videos about it, I didn't really care about social media in general. Eventually, I created a Twitter account, just to be able to follow people and see their tweets, to see what the people I cared about were... caring about. Eventually, as Discord rolled around, I first just used it to talk to my friends, but I eventually decided that a server would probably be a good idea. So I created the "Ellpeck and Friends" server, which eventually evolved into the server [that it is today](https://ellpeck.de/discord).
So, the more I used Discord and Twitter, the more... *obsessive* I became about them. Every time I'd write a tweet, I'd sit there, staring at my screen for the better part of half an hour, seeing if anyone would reply, if anyone would like the tweet, if anyone would retweet, if anyone had an opinion on what I was saying. *If anyone approved*. Every time I'd say something in my Discord server, I'd actively wait for people to respond. To answer my question, to react to my joke. I wanted to know *if anyone approved*. Every time I saw that little red dot appear next to the Discord icon in the taskbar, I'd instantly stop whatever I was doing and figuratively *run* into my server to see what was going on. Did someone need my help? Did someone *finally* reply to that one message I posted two hours ago? No, it was just a new server member who joined, triggering one of those weird "a new user joined" messages.
But... why? Why would I care so much about who liked my tweet, or who replied to it? Even if my close friends liked it, I would go "AHA, someone approves!", despite the fact that I *know* that my friends approve of most of the things I do - that's kind of what friends just do. Why did I care so much about a stupid message in my stupid Discord server? Why did I *desperately* want the person that just posted "when will you update Actually Additions to 1.15?" to reply to what I answered? They were just a stranger. It's not like their opinion actually matters when it comes to me and my life. But somehow, it still mattered *to me*.
So the other day, I just decided... no. This wasn't what I was going to do anymore. I wasn't going to let some dumb, scream-into-the-void social media platforms control my life anymore. So, before bed that night, I quickly deleted Discord and Twitter off of my computer, and off of my phone. Ever since, my phone's homescreen has had two blatantly visible empty spaces.
It's been about two weeks since I did that, and... I've honestly been feeling really good about it. I started occasionally checking Twitter again, but never just to see notifications or to see if someone replied to any of my tweets. I only ever look at it if I want to tweet, or if I'm curious about what someone else has been tweeting about recently. Last night, I opened Discord from the browser to check if any of my friends have been messaging me there. There were a couple of notifications, which I replied to. I also checked my server, and *oh my God*, did I feel good about taking a break from that. There were a bunch of messages from people asking about my mods, and how to do certain things. There weren't any important messages, anything that, if I missed it, would change my life in some way. I didn't *need* to be there.
I realized that I shouldn't care that much what other people think about me; neither on social media, nor in real life. It's an unnecessary hassle to make sure that everyone loves you, that you don't make any mistakes, that you only have opinions that other people approve of. The important part is that you approve of *yourself*, and that the people you care about also care about you.
So... if you're feeling like there's a lot of pressure on you whenever you tweet, or send a message in a Discord server, or write a Facebook post, then maybe you should try just not doing that for a little while. It might make you feel a bit better.
*This post won't have a discussion link, for obvious reasons.*
---
layout: blog
title: 🤷 But Do You Really Care?
description: On taking a break from social media
tags: [Miscellaneous]
---
Over the years, my views on social media and its purpose have changed many times. When I first started out using the internet, playing Minecraft and watching YouTube videos about it, I didn't really care about social media in general. Eventually, I created a Twitter account, just to be able to follow people and see their tweets, to see what the people I cared about were... caring about. Eventually, as Discord rolled around, I first just used it to talk to my friends, but I eventually decided that a server would probably be a good idea. So I created the "Ellpeck and Friends" server, which eventually evolved into the server [that it is today](https://link.ellpeck.de/discordweb).
So, the more I used Discord and Twitter, the more... *obsessive* I became about them. Every time I'd write a tweet, I'd sit there, staring at my screen for the better part of half an hour, seeing if anyone would reply, if anyone would like the tweet, if anyone would retweet, if anyone had an opinion on what I was saying. *If anyone approved*. Every time I'd say something in my Discord server, I'd actively wait for people to respond. To answer my question, to react to my joke. I wanted to know *if anyone approved*. Every time I saw that little red dot appear next to the Discord icon in the taskbar, I'd instantly stop whatever I was doing and figuratively *run* into my server to see what was going on. Did someone need my help? Did someone *finally* reply to that one message I posted two hours ago? No, it was just a new server member who joined, triggering one of those weird "a new user joined" messages.
But... why? Why would I care so much about who liked my tweet, or who replied to it? Even if my close friends liked it, I would go "AHA, someone approves!", despite the fact that I *know* that my friends approve of most of the things I do - that's kind of what friends just do. Why did I care so much about a stupid message in my stupid Discord server? Why did I *desperately* want the person that just posted "when will you update Actually Additions to 1.15?" to reply to what I answered? They were just a stranger. It's not like their opinion actually matters when it comes to me and my life. But somehow, it still mattered *to me*.
So the other day, I just decided... no. This wasn't what I was going to do anymore. I wasn't going to let some dumb, scream-into-the-void social media platforms control my life anymore. So, before bed that night, I quickly deleted Discord and Twitter off of my computer, and off of my phone. Ever since, my phone's homescreen has had two blatantly visible empty spaces.
It's been about two weeks since I did that, and... I've honestly been feeling really good about it. I started occasionally checking Twitter again, but never just to see notifications or to see if someone replied to any of my tweets. I only ever look at it if I want to tweet, or if I'm curious about what someone else has been tweeting about recently. Last night, I opened Discord from the browser to check if any of my friends have been messaging me there. There were a couple of notifications, which I replied to. I also checked my server, and *oh my God*, did I feel good about taking a break from that. There were a bunch of messages from people asking about my mods, and how to do certain things. There weren't any important messages, anything that, if I missed it, would change my life in some way. I didn't *need* to be there.
I realized that I shouldn't care that much what other people think about me; neither on social media, nor in real life. It's an unnecessary hassle to make sure that everyone loves you, that you don't make any mistakes, that you only have opinions that other people approve of. The important part is that you approve of *yourself*, and that the people you care about also care about you.
So... if you're feeling like there's a lot of pressure on you whenever you tweet, or send a message in a Discord server, or write a Facebook post, then maybe you should try just not doing that for a little while. It might make you feel a bit better.
*This post won't have a discussion link, for obvious reasons.*

View file

@ -0,0 +1,146 @@
---
layout: blog
title: 🍝 Ketchup, Mayo and Barbecue Sauce
description: "Sally can't quite decide what skirt to buy. When she starts going out for lunch with David, an old crush from high school, she can't quite decide what to order. But she's sure about one thing: She likes David. A lot."
tags: [Short Stories]
book: true
reedsy: https://blog.reedsy.com/creative-writing-prompts/contests/95/submissions/69321/
discuss: https://twitter.com/Ellpeck/status/1398049729582407686
---
Sally strolled along the shopping street with her favorite, rainbow-colored bag swung over her left shoulder. Looking around the shops, she wondered if she'd gotten everything from her list. Unsure, but willing to be *absolutely certain*, she pulled her phone out of her shiny bag and opened her notes. There was one item left on it, unticked: *short skirt, blue, summer*. She held the bag open with one hand and started rummaging through it with the other. Sally had already bought a skirt, but it wasn't blue, and it wasn't fit for summer either. It was longer, and the colors made her think of the fall. Of dancing around in the rain, of jumping straight into a pile of leaves. Of *Joey*.
Oh, God.
She took the skirt out of its protective casing and checked the price tag, which was still firmly attached to the waistband. Forty-seven dollars, it said. That was a lot of money to pay for a skirt, wasn't it? She jerked around on the busy street in an instant, almost bumping into a stranger, while trying to locate the store where she'd gotten this stupid thing.
Ah, there it was. Without thinking much, she stuffed the skirt back into her bag and headed straight for the entrance of the small boutique.
As she started scanning the other skirts on the racks close to the entrance, she concluded that this store was clearly ill-fitting for her goal of completing the last item on her to-do list. There were no skirts in fun, summery colors. Slightly outraged, and about ready to head to the counter to return the expensive fall skirt, she noticed a familiar face in line for the register.
Was it really--? No, it couldn't have been. As the long-haired guy turned around, she quickly ducked behind one of the racks as to not make it seem like she'd just been staring at him. Naturally. What could be a better way to make it seem like you're *totally* minding your own business than hiding under some random clothes rack like a nine-year-old?
Oh, well. It was too late now to seem normal.
As she raised her head carefully above the rack, just enough to be able to spy-- look at David, she realized how much he'd changed since the last time she saw him. He was wearing a flashy jacket, not the weird high school varsity kind, but a punky sort of dark, possibly faux-leather looking thing. His shoulders were--wow, his shoulders were broad, and they looked strong as hell. How much had he been lifting? Did he carry boulders for fun? For only a quick second, definitely absolutely not for longer, she imagined him scooping her up and carrying her like a princess. She imagined herself, staring up into his dark brown eyes, wondering what it would be like to look into them before she fell asleep.
Joey would've never done anything like that with her. It wasn't that he hadn't been strong enough to carry her or anything, it's that he just wasn't the *romantic* type. The type of person to kiss her out of nowhere, the type to turn around while walking next to her and pull her into a tight, warm embrace. But David, *oh, David*, maybe he could be that, for her.
After a few more minutes of dreaming, and possibly a tiny bit of *drooling*, she caught herself doing exactly the thing that she promised herself she'd never do again: *Pining* for someone, someone who probably didn't even remember she existed. Someone she hadn't even *talked to* yet. Jesus Christ.
She needed to get her act together, she thought to herself. And what better way to do that than to return that ugly fall skirt she'd bought and get a perfect, blue summer one just like her to-do list demanded of her. She sat up, turned away from David, who looked like he was almost finished checking out by now, and pulled the stupid skirt back out of her bag. But looking at it, she realized again how beautiful the pattern on it was. How the thin lines connected to form what looked like a network of pathways, or maybe a tree with thousands of small branches.
She stuffed the skirt right back into her bag and deliberated. She could always just get the blue summer skirt *on top* of the fall one, and keep both. But surely, she'd never *wear* both of them, because she already had so many other skirts, and dresses, and jeans. Why did she have *so many clothes*? She didn't even need to buy any more today. This whole thing was so unnecessary, she realized.
Determined to keep the skirt (because of the beautiful lines, et cetera), but not buy another skirt (because of the full closet), she headed straight for the exit with big, confident strides. So confident, in fact, that she ran right into the backside of none other than David, who had stopped in the doorway of the store, probably to check his phone or something.
Oh, fucking hell.
David leapt forward immediately and scrambled to keep his phone in his hand. He turned around and shot Sally a weird look.
"I'm so sorry," she mumbled, sounding overly apologetic. "I didn't look where I was going."
"It's no problem," he said and the corner of his mouth tilted upwards a little. "Wait, don't I know you from somewhere?"
Sally paused. A person who *hadn't* just stalked her old high school classmate from behind a fucking skirt rack would probably be confused for a second before, like her, immediately saying "Yea, hi, I'm Sally, back from school."
He arched an eyebrow. "Sally...," he began. "Sally, like, debate team Sally?"
"Is that what you thought of me as?", she said almost instinctively. "I didn't even do it for that long."
"Well," David began awkwardly and stuffed his phone into his pocket. "The other Sally made it hard to think of you as just 'Sally'", he explained.
Ah, that made a lot of sense, actually. Sally (the other one) was one of the popular girls, and *this* Sally was never that popular at school, or anywhere, really. She hung around the debate team for a while, but quit fairly quickly and moved on to a different club. And right now, still standing in the doorway of the store, awkwardly looking at David, she pulled a face and tried to remember what she *did* after she quit the debate team.
"Anyway," David said with a blank expression. "I was about to grab lunch..."
He paused, and Sally just kind of stared. She was still thinking about that damn club.
"You want to come?" David finally said.
"Uh, what?" Sally began awkwardly. It took a second for her brain to process what David had just said. "Oh, yea, sure, that sounds great!", and it really *did* sound great to her, too.
***
Sally couldn't quite believe it, but David had actually given her his *phone number* after their lunch date thing. They'd walked to a nearby fast food place and both ordered a burger and fries, and, while waiting for the food to arrive, they'd talked about what both of them had started doing after finishing school.
Sally was sitting in her bed now and looking down at her phone, the bright screen displaying David's contact information. He'd entered his first name only, along with his phone number. For a moment, Sally wondered if he might have given her some sort of fake number, but that wouldn't make much sense, because *he* was the one who'd invited her to lunch, after all.
She hovered over the green call button with her thumb, considering her options. Maybe she should just text him? What would she say, though? A call just seemed easier. Plus, it was cheaper, right? With her contract and all. Finally, she tapped the button with a shaky finger.
"Hello?" a familiar voice said on the other end of the line.
"Hey, it's me, Sally, from lunch," she said awkwardly. "Do you want to go out again, maybe?"
Wow, that was *bold* of her, wasn't it? What had gotten into her all of a sudden? She never used to be like this with Joey, or anyone else, for that matter. She shook her head and followed up with a *super* smooth "Only if you want, though. Of course." Fucking hell.
"I'd love to! I'm so glad you asked," he said. "I was worried you weren't going to call."
He was... *worried*? That was a surprise to Sally. And a nice thing to say, too, in a weird sort of way.
They decided to meet up for lunch again the next day, and David picked out another fast food place in town that sold Asian takeout. Sally wasn't sure if she was going to like it, but she knew she liked *David*, and that was the main thing that mattered to her.
Later that night, she lay awake, staring at the ceiling, thinking about David. About how nice he'd been on the phone. About lunch. *Had* he been really nice, or had she just forgotten what talking to a nice person was like? The thought was a little depressing to her, so she dismissed it as quickly as it came. No, he *had* been very nice to her, she concluded. He was a great lunch buddy.
***
David checked himself in the mirror and pulled a face. He wet his hand in the sink and tried to smooth over a rogue curl in his otherwise silky smooth, brown hair. As he let go, the stupid thing bounced back up, and he quickly admitted defeat. *Fine*, he thought to himself with a kind of confidence that he didn't normally seem to possess. *This day will go great either way.*
Sally had suggested a place that he'd never been to this time around, a place that--from the images online--looked to be a small restaurant with a large outdoor seating area. For a moment, he thought to himself if maybe she picked this place because it looked romantic.
He shrugged off the thought as a notification made his phone vibrate in his pocket. For a second, he wondered if this was Sally canceling their date-- lunch meeting-- *friendly hangout session*.
It was, indeed, Sally. Evidently, she hadn't messaged him to cancel, though.
The little banner notification at the top of his screen captured the start of her message: *been deliberating. do you mind if we go to the thai place i talked abo...* David chuckled to himself as he tapped the notification. She'd already presented him with about seventeen choices last night, asking him which one *he* would choose. What was up with that girl?
*Whatever you want*, he texted back. Damn it, did that sound too disinterested? *You're choosing today*, he added. He liked the idea of doing what Sally wanted. Of seeing her happy. Of *making* her happy. Why was he so into her all of a sudden?
*damn it*, she texted back. *reading online now that the thai place is closed, nvm*.
Fair enough.
***
David looked around the big seating area to spot Sally, sitting at a far table for two, awkwardly waving at him. He waved back briefly and made his way over to her. Sitting down at the table, he asked sarcastically, "Is this the place you want to eat, or should we check out some others instead?"
She pulled a tiny frown and rolled her eyes. "Shut up."
They continued bantering as they ordered food and until after their drinks arrived. This seemed to be their rapport now: Making fun of each other over drinks and varying cuisine. It was good. No, actually. It was *great*.
While eating his burger (and fries with mayonnaise, of course), he looked up to meet Sally's eyes for a quick moment, who smiled back before returning her focus to her own burger, also with fries and mayo. And ketchup. *And* barbecue sauce.
Jesus Christ.
He continued looking at Sally for another few seconds, studying her features, taking in her eyes, which were nestled neatly between the rims of her glasses and below her long, blonde bangs. It was warm today, and Sally was wearing a flowy, bright top and a short, blue skirt that looked like she'd just bought it the same day.
Sally looked back up at him. "You know," she began. "I've really been enjoying our dates."
*Our dates*. Did she mean their lunch meetups? Or did she really mean *dates*? He gave her the benefit of the doubt, because for him, too, it was difficult to come up with names for this... arrangement.
"Me too," he shot back before stuffing a couple of fries into his mouth. After he swallowed, he raised an eyebrow and asked: "Dates?"
She looked back up at him and smiled. "I mean, if you like. We're eating, we're talking, we're... having fun." She paused. "Isn't that what a date is?"
David let what that sink in for a moment. He looked at her and smiled. Of course, he admitted to himself, he'd been *hoping* that these were proper dates. And sitting across from Sally, right here, right now, *knowing* that she also thought of them as *dates*... it made him feel a sort of happiness that he hadn't felt in a long time. He quietly smiled to himself and looked down at his plate again, stuffing another handful of fries into his mouth.
***
As she arrived home and unlocked the front door, Sally realized that she'd been thinking about David, and David alone, for the last few days. Whenever she watched TV and saw a cute scene, she thought of David. When chatting with her friends about their relationships, she thought of David. And, yes, she admitted to herself, she'd also thought of David while touching herself. Once or twice.
But that wasn't bad, she concluded. In fact, she accepted all of it as what it was: A sign that she really liked David. She smiled to herself as she went up to her room, throwing her jacket onto a chair in the corner.
She let herself drop backwards onto the bed like a brick and pulled out her phone, holding it over her head. She opened her message thread with David and typed the words *Today was fun.* Then, she followed up with another message: *I really like you, David.*
This was, for the first time since Sally could remember, something she was absolutely certain about. She didn't have to think twice before sending those messages. She didn't even *want* to think twice about them, because it was so unusually clear to her. She liked David, and there would be nothing she could tell herself, no question she could ask herself, to change that.
Before he even read the message, Sally moved into her contact list with a few quick swipes, scrolling down to the entries that started with the letter J. *J*, as in *Joey*, of course. She tapped his name and immediately felt a weird sense of unease, a dread that she'd always felt when she had to think about him. She scrolled down past the phone numbers and his address until, right there, in the center of her screen, was a big red button with a trash can icon and the word *Delete* next to it.
She thought of the lunches with David again, the way he stuffed fries into his mouth. The way he snickered when she asked the waiter for three different condiments. The way his smooth hair dissolved into more and more curls the longer he was outside in the dry summer heat. The way things seemed so *easy* with him. All of a sudden, the dread that Joey's name brought up in her vanished, just like that.
And then she pressed the button.
As she watched Joey's name slide off-screen in the contact list, all the other J names shuffling up and down neatly to fill the gap, a banner notification came down from the top of her screen. It was David.
*I like you too, Sally. Really like you.*
Another notification came down. *Lunch again tomorrow?*

View file

@ -0,0 +1,190 @@
---
layout: blog
title: 🎉 Vince, a Party and Brandon
description: "Vincent is an anxious introvert who is desperate to find a guy to have some fun with. At his best friend's party, he gets the chance to do just that, but his anxiety threatens to screw it all up."
tags: [Short Stories]
book: true
mature: true
reedsy: https://blog.reedsy.com/creative-writing-prompts/contests/104/submissions/78425/
discuss: https://twitter.com/Ellpeck/status/1419838270909865985
---
After dreading it for multiple hours now, it was finally time for Vincent to read the messages that his friend had been blowing up his phone with. It was a little difficult for him, because the conversation that he'd anxiously, but silently, opted out of was regarding the party that Myra was throwing later today. Vince rolled over in his bed and reached blindly for his phone, which he was sure had fallen onto the ground at some point in the afternoon. Unlocking it, he was immediately bombarded by about thirty-seven thousand messages from his friend.
**are you coming tonight**
**hey, stop ignorign me**
**read my messages you cheap ass**
He groaned and rolled over again, kicking the blanket onto the floor with his feet. For a moment, he considered just saying no. But he wasn't going to let himself give up that easily. Not again.
*i dont know ok*
*youre just gonna talk to someone else*
*and ill be all by myself*
The little tick marks turned blue, and his face followed suit quickly when his phone began to vibrate violently in his hand. A call? Really?
"If you don't come, I will literally cry, Vincent. What the fuck am I going to do without you?"
Myra genuinely sounded a little annoyed, but Vince knew this game: She'd pretend to be pissed at him for a minute until he started feeling genuinely bad, and then she had to explain to him that she was just joking, like always. It was less of a game and more of a *Myra being a normal friend while Vince stumbles around his inability to be a functioning person*. So, pretty much like always.
"Damn it, Vince, you know I'm not being serious, right?" There it was. "You're the only person who it's fun to drink with."
Admittedly, he *was* pretty fun when he managed to leave his anxiety behind at the bottom of a glass. And drunk Vince and drunk Myra would always start deep, meaninglessly meaningful conversations that made them think deeply and then cackle embarrassingly again and again.
And if he said no to this party, he would've not only denied his best friend, who was there for him pretty much constantly, but he'd just have to have another sad Saturday night consisting of chips, memes and maybe a, uh, *fun toy* and some porn. Admittedly, that sounded a lot more fun than some jock-infested party.
"Look, listen," Myra began after enduring the awkward silence for longer than she reasonably should have. "There's this guy coming to the party who is really sweet, and he looks pretty cute too. Dan even said that he might be, you know, *a top*, and I know how desperate you've been for that sort of thing recently."
Damn it. Why did Vincent tell anyone anything, ever?
"Why do I tell you anything, ever?" He half-whispered into his phone and sat up into a surprisingly graceful cross-legged situation and picked up the dildo that he spotted at the foot of his bed. He really had to take better care of his belongings.
"I'm not sure. It's weird. But no judgement, babe. I know what it's like."
"You literally just said that it's weird, how is that *not* judgement?"
"Listen. Are you coming or not? I'm not going to spend the rest of the evening getting your sad ass laid."
Vince rolled his eyes and sighed, deliberately loud enough for her to hear it clearly.
"Stop rolling your eyes at me. Yes or no?"
She knew him too well. He looked at the toy in his hand and fondled it a little. *Non-sexually* fondled it. He really *did* want to do stuff with a guy again, but he also quite enjoyed the idea of not having to go out and meet new people.
"Fuck, fine," he said and flopped back onto his back. Goddamn it.
"There we go," Myra exclaimed loudly and immediately hung up.
There we fucking go.
***
Myra's party turned out to be happening at a different house that apparently belonged to a friend of hers or something. Vince was kind of relieved, as Myra's apartment didn't feature a lot of space to hide from sweaty crowds of drunken people in. The night was unfurling as people around him started showing clear signs of a buzz, and of course, Myra was absolutely nowhere to be found. Great.
In his frustration, Vince decided to sit down on one of the chic living room sofas that were very clearly too expensive to house his poor ass. Nevertheless, it seemed to invite him to take a little break from the crowds and the anxiety and just *breathe* for a second.
If he was being honest with himself, Vincent wasn't at all interested in this party or the people at it. He sipped his drink tentatively, a vodka and coke that tasted more like a vodka and vodka with a side of coke, and looked around the bustling living room. Not only had Myra seemingly disappeared, any other people that he may know and be able to interact with were also nowhere to be found. And while this meant that there were *also* no people around that he might want to run-away-slash-hide from, it was still kind of a bummer to be wasting his night at this place and still sit around alone.
A few more minutes went by, and Vince started spotting one or two people that he *did* know, but none of which he wanted to talk to. There was a guy that he'd briefly hooked up with at another one of Myra's parties about a year ago (he was one of the "straight, but I have needs, too" guys), and there was a girl who had flirted with him a lot before Vince found out that he was, in fact, not at all interested in her or her gender. They'd still continue talking every now and again after that, but it seemed to Vince like she'd only been interested in him as a potential romantic (or sexual) partner, which was fair enough.
Myra finally reappeared by tapping Vincent on the shoulder from behind the sofa. She was radiating confidence and also, maybe, probably, a slight bit of drunkenness. Those tended to go hand in hand for her.
"Hey, stranger," she said to him as if they hadn't talked for, like, months, and not literally less than half an hour.
"Is this a rickroll situation, and you just forgot the actual lyrics, or what's going on with you?" He said to her with a snarky grin.
"Shut up," she said, jumped over the backrest and landed on the soft, brown leather beside him. She came a little closer and tilted her head to whisper into Vince's ear.
"Okay, listen. Brandon is here," she said.
Vince made one of his famous faces. "Who the hell is Brandon?"
"The guy I was telling you about! The cute one with a dick that isn't made of silicone?"
Ah, yes. The human dick man.
"Couldn't you guess from the *name*?" She laughed. He did not.
He chose a smooth "How much have you had to drink?" as his reply and then pushed her head off of his shoulder. "Where is he?"
"He's, uh..." She narrowed her eyes and began to investigate the crowd.
"Right there!" She exclaimed and jumped up from the couch. "Go to him! Come on!"
***
Brandon was, in fact, quite cute. Actually, Vince might have been drooling over him just a tiny bit while waiting for Myra to resurface earlier, even before he knew that he was the cute, gay friend in question. Well, he might have been bi or pan or something. That wasn't important right now.
What *was* important right now was getting Brandon out of the hot tub and into one of the spare bedrooms. Why was there even a *hot tub*? Had someone taken this house from a high school movie? Though, in the hot tub's defense, it did allow Vince to see Brandon's not-quite-chiseled chest and his not-so-short hair that was flowing over his surprisingly broad shoulders. He hadn't quite decided if he found Brandon cute or hot yet, but he *had* decided that he would quite enjoy spending some more time with him to figure it out.
Vincent, being the perfect-at-social-interaction smooth talker that he was, stood in front of the hot tub like an idiot, just staring at Brandon's beautiful fucking face. How did humans do that *speaking* thing again?
Brandon turned around and saw Vince standing there like an idiotic statue. He looked at him for a second and then turned to jump out of the hot tub gracefully, with little streams of water running down his chest and over his abs and dripping off of his Bermudas. That's just what water usually did, but it looked fantastic on this guy. Of course.
"Oh hey," Brandon began, to Vince's absolute surprise. He tried to snap out of his continued statueing and blinked. "You're Vince, right? Myra told me all about you."
"Yup, yea, that's... that's me. Hi." He stood there awkwardly, debating whether he should extend his hand? Go in for a hug? No, he was dripping wet. The--- *Brandon* was dripping wet. Yes.
Not too long after the most awkward thirty-five seconds of his life, Vince found himself sitting next to Brandon on a bed in a room that Vince was pretty sure was at least twice as big as his own apartment. Brandon had, despite Vincent's wordless disapproval, towel dried himself off and put on a shirt.
After staring at each other for a few seconds, their thighs rubbing together on the bed, things started going pretty fast. Brandon took of Vince's shirt and then his own, and Vincent started making use of his free hands to feel Brandon's rather muscular body. Brandon took off his Bermudas and they both realized that he was not wearing anything else underneath, revealing his rock hard---and very human---cock. Vince also took off his pants after struggling with his belt buckle for an embarrassing amount of time, and then he found himself laying on his back with Brandon's chest pressed against his, their cocks rubbing against each other. Brandon got up from the bed and grabbed Vince's legs, pulling him towards the edge of the bed.
Vincent knew where this was going, and he really, *really* wanted to like it. But as Brandon was pulling Vincent closer to his throbbing cock and bending his lower legs to rest them on his own shoulders, some sort of switch flicked inside Vince's head. Yes, of course he wanted to have his brains fucked out by a hot guy. By *this* hot guy. But he didn't want it right now, like this, so fast and without a single question asked.
Brandon was fumbling with a condom wrapper and Vincent quickly decided to use the moment to slide up a bit towards the head of the bed, taking his legs back and starting to hug them.
"Can we," He began. "Can we slow down a bit? I don't think I'm ready for that yet." Vincent frowned and looked at Brandon, whose expression was unreadable to him.
"What?" Brandon asked, still expressionless. His tone wasn't aggressive, but it wasn't all that calm, either. Had Vincent said something wrong?
"I thought this is what you wanted. That's what Myra said!"
What the fuck? Was Myra, his best friend, just going around telling random guys that Vincent was desperate to be *fucked* by some stranger stud?
Vincent started breathing heavily. It took a second for him to start speaking, but then he did. "Oh, and you just signed the fuck up right away, didn't you? Just ready to fuck some random guy and then never talk to him again? Is that what you do every weekend?"
Brandon frowned and closed his eyes. He sighed. He didn't say anything for a solid thirty seconds, which gave Vincent plenty of time to develop his famous instant regrets. Even if Brandon *was* that sort of guy, what did it matter to Vince? He *was* a little desperate to be fucked, and this stranger seemed a hell of a lot better to him than any of the other strangers at this party. And Myra had said that he was *sweet*, too. Maybe something could have happened here. But he just ruined it, officially and irrevocably. Right?
"Fuck," Vincent began again. He shook his head slowly and frowned. "I'm sorry, that came out wrong."
Brandon just stood there. "It did, didn't it?"
Then he walked away.
***
When Vincent trotted slowly down the stairs, the party was still in full swing, and he absolutely hated it. Not only were there a million humans that all reeked of alcohol and just generally existed, which was usually bad enough, Vincent had also ruined a very good opportunity to have sex with a very hot and, according to Myra, very sweet guy. A guy whom, even after looking around the living room and the kitchen, he wasn't able to find anywhere.
This was, as Vincent told himself, the perfect time to give up. Find Myra, say goodbye, and head home to his bed and his stupid toys. He didn't deserve human dick anyway, because all he could do was *be* a human dick.
If he talked to Myra right now, all she'd tell him would be to stop wallowing in self-pity and do something about the problem or some crap. Maybe she'd instruct him to find Brandon, or find some other guy, or apologize through text because she probably had his number, too. But Vincent wasn't ready for rationality right now. Being mad at himself felt good, in a fucked up way. And maybe it had its purpose, or at least that's what he began telling himself as he was walking outside to check the hot tub for any signs of the guy he'd just hurt for no reason.
Brandon was, in fact, in the hot tub. He sat there with his hands by his sides, less happy and less confident than the first time he saw him here.
Vincent slowly walked up to him. He closed his eyes for a second and started feeling sick to his stomach. If he was going to leave, he should leave *right now*. But that's what he usually did, and he wasn't going to mess this up again. He wanted to give this guy a chance. He wanted to give *himself* a chance, too.
"Hey, uh, Brandon?" He said and Brandon turned around. He narrowed his eyes as he looked into Vincent's. "Can we talk?"
***
Vincent and Brandon were sitting on a log bench at the far side of the unreasonably large backyard.
"Fuck, I'm so sorry," Vincent said apologetically, again. "I just... I thought about something stupid and then I snapped. I really didn't mean what I said."
Brandon looked at him and frowned, his eyes twitching back and forth between the ground and Vincent's face.
"No, you were right, man. I should have asked if you wanted to do the stuff we did. I was too fast." He didn't sound disingenuous. If anything, he sounded like he was mad at himself.
"I *did* want to do the stuff you did. I really wanted to. I *still* want to." After saying that, Vincent decided it was probably time to shut up. Was he being too vulnerable or honest now? Was Brandon just going to walk back to the hot tub, or go inside and fuck any of the twenty other cute guys that were less of an emotional mess than Vincent was?
Brandon chuckled and smiled.
"Okay," He said. "Let's do it, then."
"Wait, really?" Vincent blurted out and raised his eyebrows to the fucking sky.
"Yea. I still want to, too. Plus, I haven't really gotten to take a proper look at your ass yet."
"Hey now, I'm not *just* an ass, Brandon." Vincent said and smiled.
"So far, you were," Brandon whispered into his ear, and Vincent knew that it was true, but he wasn't sure if this was Brandon dirty talking or just... telling him how much of an ass he was.
It didn't take long until he found out, because Brandon turned Vincent's head towards his own.
"Can I make out with you?" He asked quietly and bit his lip.
"Yes," Vincent replied with a grin, and he kissed Brandon on the lips and didn't let go.
Eventually, their tongues were intertwined and only momentarily separated as Brandon made sure that he was doing everything exactly as Vincent wanted him to. They went slower this time, but still did all the things that they'd both wanted to do.
When they were done, they were both sweaty and out of breath. They cleaned themselves (and the bench) up with some tissues that Vincent found in his back pocket and kissed a final time before walking back towards the house-slash-mansion together.
Vincent looked at Brandon, who was walking next to him, and quietly said a genuine, vulnerable "Thanks."
Brandon looked back and chuckled. "It was fun." He said. Then he came a little closer.
"Hey, Vincent," He began. "Can I take your hand?"

View file

@ -0,0 +1,91 @@
---
layout: blog
title: 📚 My Favorite Reads of 2021
description: I've been using 2021 to read some more again. As it turns out, there are quite a few really good books out there. Here are some of my favorites I read in 2021.
tags: [Reading]
mature: true
discuss: https://twitter.com/Ellpeck/status/1467156754194653191
---
2021 has been a rough year. For all of us. I decided to use it to do some more reading again. This is a list of some of my favorite books I read this year. As you'll be able to tell pretty quickly, my main interest is very gay romance books. *What a surprise.*
I compiled this list by going through my [StoryGraph](https://app.thestorygraph.com/profile/ellpeck) 5⭐ reviews and picking the ones that I thought it would be most interesting to talk about. There won't be many, if any, spoilers, but I'll definitely be summarizing the plot of these books in this post and putting a few funny quotes. These also aren't in any particular order, either.
# 🪲 Boyfriend Material, by Alexis Hall
[Boyfriend Material](https://app.thestorygraph.com/books/4ecc34c0-04e1-420f-a303-2a900063397e) was the first book written by Alexis Hall that I had the pleasure of reading. And I found out pretty quickly that he is an absolutely *fantastic* author. His writing is incredibly funny, but also deeply emotional and touching in certain places. It's an amazing mix.
Boyfriend Material is about two guys who have absolutely no business falling in love... falling in love. Luc works at a charity for some sort of beetle that has (surprisingly) little significance in the story, and Oliver is some sort of fancy lawyer-type person. Luc's friends are incredibly funny (not just their personalities, but also the way they're presented), and the way that Luc and Oliver interact is delightfully embarrassing, but also incredibly cute.
There's also two characters called James Royce-Royce and James Royce-Royce. I promise I'm not making that up.
> I met James Royce and James Royce (now James Royce-Royce and James Royce-Royce) at a university LGBTQ+ event. In some ways, its strange the two of them work so well together because their name is pretty much the only thing theyve ever had in common.
The book is a great first step in discovering the amazing, witty and hilarious writing of Alexis Hall. There's at least three of his other books that I'd love to talk about here. I promise I won't. But actually, I will. Two more of his books are coming up.
# 🚇 One Last Stop, by Casey McQuiston
[One Last Stop](https://app.thestorygraph.com/books/28386517-30cc-4ce4-bb90-9336c370a9dd) falls into a genre that I usually can't quite get myself to enjoy: Somewhat far-fetched science fiction. I'd previously read one of Casey McQuiston's books, namely [Red, White and Royal Blue](https://app.thestorygraph.com/books/d9b32ad2-41d2-4663-a19c-f65ffc7b0091) and absolutely adored it, and so I thought it was a good opportunity to discover some more of their writing.
One Last Stop is about a girl, whom I will refer to exclusively as Coffee Girl, who moves to a big city and immediately falls in love with a girl she meets on the train, whom I will refer to as Subway Girl. There's a bit of a problem, though: Subway Girl is *not from this time*. Spooky. She's actually stuck in the train due to some sort of time... problem that I shan't go into because, to me, it is very far from the most interesting part of the book.
What I enjoy most is the way that the main character struggles with this issue, and the way her emotions are depicted, not only in relation to Subway Girl, but also in relation to her hilarious queer friends, her chaotic and possibly not-very-good mother, and the ever-present dread of having to go to University instead of hanging out with the girl on the train for all eternity.
> “I swear to God, if a ghost kills me, Ill haunt the shower,” Wes says. “You guys will never have hot water again.” “We dont have hot water now,” August points out. “Fine, Ill haunt the toilet.” “Why do you want to haunt a bathroom, man?” Isaiah asks. “Its where people are most vulnerable,” Wes says, like its obvious. Isaiah frowns thoughtfully and nods.
Please read this book, *especially* if you enjoy the sci-fi aspect more than me. Which I'm sure you do, given I ignored it as best I could.
# 🕍 Jews Don't Count, by David Baddiel
[Jews Don't Count](https://app.thestorygraph.com/books/e13bcb3b-a0ea-474e-8259-e55d1b0fe4ef) is the only non-fiction book on this list, because it turns out that I enjoy fiction far more, given it's usually a lot less depressing.
Now, I'm not jewish. I feel the need to point this out right off the bat because I'm not a big fan of talking about political problems that I don't have any personal experience with. That being said, I *am* from Germany, and the troubles surrounding antisemitism have been absolutely drilled into our heads as schoolchildren. Even moreso did it make me feel very uncomfortable about my own observation skills, as well as my own (lack of) political interest in this topic, that this book proves that antisemitism goes mostly ignored when it comes to debate around racism, religious freedom and cultural identity.
If you want to educate yourself on political topics that you don't have much personal experience with (as you should!), Jews Don't Count is a great read. It's aimed at leftists like me and probably you, and it aims to shine a light on the way that antisemitism is treated in the Western world in the current times. As it turns out: It's treated very poorly. The book, however, treats the topic it's about with respect, brings up a lot of current day examples to illustrate the issues, and is only about 150 pages long, making it deliver a very quick, but also very powerful, message.
# 🎂 Rosaline Palmer Takes the Cake, by Alexis Hall
I've talked about Alexis Hall before. In fact, it was this very blog post. Just... a few minutes ago, actually. But here I am, yet again, talking about his absolutely brilliant writing.
[The book](https://app.thestorygraph.com/books/bbac9ac1-8d25-4413-8cbd-acb82e79cb67), whose title is far too long, is about Rosaline Palmer, a bisexual single mother who makes the terrible (but great) decision of joining what is essentially a not-so-trademarked version of the British Bake-Off, only with exactly the same amount of chaos and hilarity. When that isn't currently the main thing messing up her life, it's also about her relationship with a very, uh, *lovely* man, the relationship with her daughter, and the relationship with her excellent and sexy friend Anvita (her words, not mine).
Here's a lot of quotes, because *Jesus Christ, this book is funny*. But also very real. I'm not quoting the real parts, because they're not as funny. But it gets very emotional, and very "there's a lot of problems with the way bisexuality is perceived by straight people", and very "life is hard sometimes". But this is just some funny parts. Don't wanna bring you down.
> Grace Forsythe tried to give her a reassuring look from the opposite side of a cake that was rapidly turning into a landslide. “Its fine. Ill just stand here holding it for the rest of my life. You can tell the judges Im an especially elaborate fondant decoration. Which, now I think about it, is what my ex-girlfriend used to call me.”
> To her horror, she was actually crying. And the next thing she knew, Grace Forsythe was gently removing the spoon from her hands. “Fuck shit piss wank bollocks drink Coca-Cola buy Smeg ovens legalise cannabis abolish the monarchy. Oh sorry, did I ruin the segment? What a shame. Maybe go film someone else for a bit.” The producer and camera operator dutifully departed. Rosaline drew in a shaky breath and wiped her eyes. “God, thank you.”
Hi Alexis, if you're somehow reading this: I am *begging you* to make a second book that is just about Grace Forsythe and her life. Please. I'll do anything.
# 🏨 Act Your Age, Eve Brown, by Talia Hibbert
Talia Hibbert is also one of my favorite authors. She writes brilliantly about diverse people, be that sexuality, race, personality or illnesses. Out of the three Brown Sisters books, [Act Your Age, Eve Brown](https://app.thestorygraph.com/books/8a97c89d-1436-4563-bc1f-e44836a750b9) is by far my favorite.
Act Your Age, Eve Brown is about Eve Brown, who stumbles into a... hotel-slash-inn ran by a particularly charming, but also (to her) extremely irritating, young man named Jacob. As it turns out, Jacob doesn't quite like Eve either... *at first*. If you know what I mean.
The book is written from both perspectives on a per-scene and per-chapter basis, and it is a fantastic read. The relationship between the two main characters is hilarious, and with Eve actually *getting a job* from the very guy whom she *ran over* with her car (yes, really), it is quite the rollercoaster, to say the least.
Also, the writing is hilarious. Just look.
> Jacob Wayne was never screwed. Well, not like that—obviously he was sometimes screwed in other, better ways. Although not as often as hed like, but—you know—ah, fuck it, never mind.
> There was that voice again, strange and yet familiar. His mind was hot and sticky like fudge. Yum, fudge. Was this a guest, maybe? A yummy, fudgy guest? Fuck. No lying around in the street in front of guests. It was inappropriate and irresponsible and very bad business.
Yum, fudge.
# 🔎 Murder Most Actual, by Alexis Hall
[Murder Most Actual](https://app.thestorygraph.com/books/3f146f4d-136e-4c75-9cd7-4bdd50aa25b3) is the last of these books I read. It's by Alexis Hall, whom you may have heard of from the last seventeen thousand times I talked about him. He's brilliant, blah blah blah, you've heard this already, but what you haven't heard yet is the fact that I downloaded an e-book from a site I'd never used before, made an account and everything, and then painstakingly shoved it onto my Kindle, so I could read it. As it turns out, it was worth it. Very. In fact, this is probably his best book yet. Maybe. I'm not sure.
Murder Most Actual is about murder, but in a most actual way. Yes. It follows a couple of lesbians (as in "a lesbian couple", not, like, 3 stray lesbians), because everyone in Alexis Hall's books is gay (which is not criticism, it's exactly the way I want it to be), as they arrive at a hotel that is hosting a fun murder mystery dinner. The only problem is that it's not so much a murder mystery dinner as *multiple murders*, *multiple mysteries* and an ever-growing shortness of dinner. Over the course of the book, the relationship between the two main characters redevelops into something absolutely adorable, and they also solve the murders. Somewhat. With the help of a particularly disgruntled police guy, who I think should also get his own spin-off eventually.
It's also, and I know I'm starting to sound like I'm repeating myself, *incredibly funny*. Getting these quotes onto my computer was an absolute nightmare, by the way, becasue the notes and highlighted sections from my Kindle don't sync with my computer because it's not a... proper Kindle... Amazon... book. Thing.
> Liza was suddenly aware of an absence at her side as Hanna stepped forward to join the free-for-all. “Just a wacky, out-there suggestion somebody might want to jump on. Could we, maybe, call the police?”
> Now Sir Richard was looking genuinely crestfallen. “You know, between the two of you, youre almost taking all the fun out of this.” “Out of the dead man?” Hanna clarified.
> Neither the professor nor the killer had been courteous enough to wear shoes that were notably large or small, or characterised with a distinctive tread, or perhaps conveniently embossed with the name of a manufacturer who only ever made three pairs of shoes a year for a very select list of clients.
Also, I love Hanna. Just wanted to say that. *Also*, each chapter has a fun Clue(do)-style title with a person, and an object, and a location. It's great. I love it.
# So Yea,
I think the main thing we learn from this list is that I love romantic fiction, and I love Alexis Hall. I'm sorry, it's just all so good.
Anyway, I hope this post was interesting to at least a small subset of you. I know that this isn't what I regularly talk about, and this post contains a surprising lack of programming-related things (there you go, now it contains programming-related things, sort of), but reading is also something I really enjoy doing. Although I do have to admit that I'm not very good at doing it consistently.
Either way, thanks so much for reading, and if you're Alexis Hall, please write more books. No pressure, though.

View file

@ -0,0 +1,64 @@
---
layout: blog
title: 💻 Your Minecraft Modding Questions, Answered
description: I asked yall to send me your modding and programming questions on Twitter and Discord, and in this post, I try to answer all of those.
tags: [Minecraft, Featured]
discuss: https://twitter.com/Ellpeck/status/1373660751060160512
---
Hi, I'm Ellpeck. I'm the creator of a bunch of Minecraft mods, including Actually Additions, Nature's Aura and Pretty Pipes. A while ago, I also started taking [commissions](https://ellpeck.de/commissions), so the list of Minecraft mods I've created is slowly, but surely, growing.
I asked all of you on Twitter and in [my Discord server](https://link.ellpeck.de/discordweb) if there are any modding and programming-related questions you'd like me to answer. Since most of the questions yall ended up asking were related to Minecraft modding, I thought I'd make the post exclusively about that after all. It also means that you might find this post interesting even if you don't program or make Minecraft mods yourself, because the questions ended up being pretty general.
Here goes.
# How do you keep yourself interested
> ...in an idea or project, so you won't abandon it after two days of enjoying it? (canitzp)
Hahaaa. I really wish I had an answer for that. I have *countless* abandoned projects, as do most people that work in creative fields in some capacity. I recently made most of them public on my [GitHub](https://github.com/ellpeck), so if you go there and scroll back far enough, you'll find some old games and unfinished mods.
So how *do* I keep myself interested? After all, I've made and finished a bunch of stuff. Since I suffer pretty massively from depression and anxiety, it's especially hard for me to commit and pull through on anything, really. However, there's a huge thing I've learned: If you start losing motivation for something, *don't* try to fight it. If you do, you'll just force yourself to work on the project without actually being interested, causing you to burn out with it even more. Instead, accept that you're losing interest and move on to something else. While the first strategy has lead me to abandon projects entirely several times, the latter only caused me to take fairly long breaks, but so far, I've always returned to projects that I didn't try to push myself to work on.
# Good tips before diving into modded Minecraft?
> (Afkman57)
I'm not sure if you mean *playing* modded Minecraft or *creating* mods, so I'll just answer the question for both!
Before diving into *playing*, I think it's really important to know what you're getting yourself into, because expectations can pretty drastically change your experience of things. (Most) Minecraft mods are *not* commercial products, and they *don't* have quality control. Authors don't get paid for creating mods, and they don't *owe* you anything just because you downloaded their mods. I think this is very important to realize because I've interacted with so many people before that think if you download a mod, it's like you're buying a product with the promise of life-long support. If something breaks in a certain way, or something works in a way that you dislike, but the mod's author doesn't agree with what you see as a problem, they have *no* obligation to change it for you. On the other hand, modded Minecraft can be *incredibly* fun. With well-made mods, there is so much new stuff to discover and create that it feels like you got yourself an entirely different, new game.
Before diving into *creating mods*... well. The most important piece of advice I personally have for you is: **Learn the language first**. If you're creating mods for Java Edition, *please* have at least a basic understanding of Java first. The first time I tried my hand at programming was for a Minecraft mod (Actually Additions' sort of roots, actually) and it went horribly, because I didn't understand what was part of the language I was using and what was part of the rather complex Minecraft and Forge API. For example, because Minecraft makes heavy use of method chaining[^1], when I first started out, I used to think that that was just an inherent property of the language (which obviously makes very little sense). I don't know if that was just me being a bit slow, or if that's the kind of misunderstanding that comes from not really learning a language before diving head-first into a project that uses it.
# Maybe some good guidelines
> ...on code-format or even conventions for forge-modding. (Afkman57)
Oh boy. Talking about conventions is always a tough one, because people disagree so fundamentally on what is good code and what isn't. A great example was when I learned that, in professional software engineering, early returns[^2] are apparently considered "bad practice" because they "decrease readability." How decreasing the amount of nesting (and thus, the amount you have to scroll to the right by) also decreases *readability*, when I think it massively *increases* readability, I'm not sure. Maybe that was just my software engineering professor having a field day, though. I really hope it's that.
When it comes to programming in languages that don't *rely* on formatting for their syntax (like Python), the actual formatting you use doesn't matter, even though some people might have you believe that it's *extremely important* to put opening braces on a new line. For me, the only important thing is that **you keep it consistent**. If you use an IDE, your best bet is to configure its auto-formatter exactly the way you like, and then using the format button every now and again (or even turning on format on save!) to make sure that, no matter how horrible your formatting is, at least it's consistently so.
# Any useful tools, resources, utilities etc.
> ...for modding would be good too. (Afkman57)
I think the best, and most important tool, for modding, and any kind of programming, is a git server, be it GitHub, Bitbucket or whatever else. Source control is amazing, because not only does it allow you to see exactly what you (and your colleagues) changed an when, but it also allows you to revert any of your changes or merge them together.
The other very useful thing about git websites like GitHub is that you can *look at other people's code* and take it as an implicit sort of tutorial. There are countless times where I looked at the code of other, bigger mods in an attempt to find useful information on how to realize certain things in my own mods. In the very first version of Actually Additions that had [Laser Relays](https://ellpeck.de/actaddmanual/#laserRelays), they were pretty much an exact copy of [Immersive Engineering](https://www.curseforge.com/minecraft/mc-mods/immersive-engineering)'s wire system, in terms of their inner workings. When you look at and especially copy other people's code, though, make sure of two things:
- *Understand* the code you're copying. If you don't understand it, you might as well just be mindlessly copying random code from random places.
- Keep the code's *license* in mind. Some licenses allow you to copy code freely, some require you to give credit and some don't allow you to copy any code at all. Even if a project allows you to copy code without giving credit, it's always good practice to do so anyway.
# How much work did it take to make Actually Additions
> ...from start to finish? Also how long did it take you to go from an idea to a first "prototype"? (Spo0ok)
This is an incredibly tough question, because of course I didn't record an exact measurement of how many hours I sunk into creating Actually Additions. What I do know, however, is this: It look *a long time*. Actually Additions was one of my first mods, and as previously discussed, I was not very good at any kind of programming when I started out, so I made al ot of mistakes, and I copied a lot of code without understanding it. That being said, I can give a rough estimate based on data that I already discussed in my [post about its features](https://ellpeck.de/blog/actually_additions).
The first file I uploaded to CurseForge was [this one](https://www.curseforge.com/minecraft/mc-mods/actually-additions/files/2229705), on *March 15, 2015*. I know that, before I started uploading the mod to CurseForge, I had a Minecraft Forum page (it was still that time in 2015), and I would release early versions of the mod on Mediafire or some other site. That wouldn't have lasted that long, though, and looking at the commit history for the mod, the first time I referred to it as Actually Additions wasn't long after the repository was first created, [in March 2015](https://github.com/Ellpeck/ActuallyAdditions/commit/2a2308c0ce35fe8a5ac9dcea100b9aeb941c495a). I made my last real contribution [in June 2017](https://github.com/Ellpeck/ActuallyAdditions/commits/main?after=896a082d747a3e19755ded1973544d59fa992787+244).
That means, to create all of the features that the mod currently has, it took me about two years. I know that measurement is pretty reasonable, because I took almost no breaks when I first created the mod. This information also means that I have now *not* worked on the mod for longer than I originally *did* work on it. It's been like that for a long while actually, because 2017 is surprisingly far in the past. That's honestly kind of scary. I'm getting old, aren't I?
---
So yea, that's about it in terms of modding-related questions. I hope you found this post enjoyable. If you did, and if you have more questions for me to answer, you can always reply to the discussion tweet linked right at the end of this post, and if there are enough additional questions, I might make a second one.
Anyway, thanks for reading! ❤
[^1]: Method chaining is a concept that is used in a few languages, but especially Java. The idea is that, when you create non-static methods that change certain properties about an object, you make the method return *the object itself*. This allows for multiple method calls to be done in one line, which is especially useful when creating a system where you apply a bunch of settings to an object. `new Block().setHardness(3).setLightLevel(15);`
[^2]: Short-circuiting out of methods using `return` instead of having a bunch of nested checks

View file

@ -0,0 +1,143 @@
---
layout: blog
title: ❤️ Em & Ben
description: Ben hates public gatherings because he suffers from anxiety. He's also in love with his best friend Emily, who frequently tries to drag him out of his shell. At the spring festival, he has a panic attack. Will Emily manage to support him?
tags: [Short Stories]
book: true
reedsy: https://blog.reedsy.com/creative-writing-prompts/contests/86/submissions/59997/
discuss: https://twitter.com/Ellpeck/status/1374770369169653767
---
# Chapter 1
There'd never been a day quite like Tuesday, February 17. It was a warm, spring day, and the newly awakened sun was warming Ben's freckle-ridden face. He didn't know it just yet, but this would be the day that Ben would almost die. Well, that's the way he'd think of it *afterwards*, anyway.
The park was unusually packed with people, and for a second, Ben debated whether turning back was a good idea after all. He'd had these thoughts a lot recently; wanting to just turn back and run away from things. He ran a hand through his long, silky hair and turned around. *Damn it*, he thought, before he snapped back to facing the center of the park. *I have to do this.*
From afar, he could just about make out what was going on around the large fountain. The water was splashing out of what he'd always just assumed to be a large fish, though he didn't have the ability to tell for sure, because the fountain was old and rugged and had probably been just-about-fixed one too many times before. Ben squinted a bit before raising his glasses in an attempt to make out the faces of the people he saw, sitting and standing around the fountain, talking. He was looking for his best friend, Emily. She was really the kind of person that would drag him to outings like this; public gatherings and any sort of parties were terrifying to Ben. It was natural then that he would find a best friend that is his absolute polar opposite: Popular, blonde and, of course, an absolute party animal. *Damn it, Em, where are you?*, he mumbled to no one in particular.
He started approaching the fountain and the square around it. Several small crowds of people were standing around little stalls that emitted vastly different smells. Amidst all of this mess, he was almost certain he could smell bubblegum ice cream. Hectically looking around in an attempt to find his friend, he tried to suppress any memory related to bubble gum. To ice cream. To the life that he'd had before he changed. *Fuck*, he quietly said to himself.
The smell of the bubblegum ice cream started getting too strong for him to handle, and memories started welling up inside of him. His brain was unfolding like a book, too heavy to close. Ben pictured a red, rickety swing set in the middle of a park much like the one he was in right now. He pictured Emily, and for a moment, he pictured the way it had felt. The way the wind blew through his hair as he threw his legs back and forth on the swings, laughing with Em about wanting to do a three hundred and sixty degree turn. Had he already called her *Em* back then?
He immediately snapped back into reality when he felt the tap of something on his shoulder. Immediately might have been generous, because the tap really did feel like a heavy knock. Maybe it had taken him a few seconds to come back. His eyes felt a bit watery and his knees were shaking, but he told himself that he was probably just hungry (he wasn't) and turned around to discover the source of the heavy tapping.
"Ben!", Emily exclaimed right behind him. She was a beautiful woman with curly hair and a smile that probably tasted like bubblegum ice cream. *Tasted?*
"Hey, Emily, why'd you take so long?", he asked in the flattest possible tone.
"Shut up", she returned with a snarky grin. *Damn*, she looked extra beautiful today. Ben looked down at her flowy, blue skirt and matching light purple ballerinas. Even though his eyes were still watery, he couldn't help but smile at the look of her, standing there right in front of him. This is exactly what she did to him.
"Oh man, I want some fro-yo right about now!", Emily sighed yearningly as she grasped Ben's upper arm and started pulling him towards one of the stalls by the fountain. "You want some too? I can pay."
"Are you kidding me? You're *broke*, I'm not letting you pay for *anything*!"
"*Broke*? Come on, that's a bit harsh", she countered and frowned. She stopped dead in her tracks, let go of his upper arm and, after a few seconds of fondling with her cardigan, produced a thirty-dollar bill out of what he could only assume to be a hidden pocket somewhere. "See?"
Not entirely convinced of Emily's sudden wealth, he raised an eyebrow and tilted his head slightly in disapproval. "Fine."
# Chapter 2
Over the next few hours, Ben was dragged back and forth between stalls and attractions. For every single one, he was apprehensive at first but rather quickly changed his mind once Emily frowned. It was such a heart-melting frown, worse than the faces that his aunt's dogs would make when they wanted to be taken for a long walk. Every single thing about Emily was just so beautiful that he could be happy doing anything with her. Even going to a shoddy, uncomfortable carnival in the middle of February.
They'd already stopped at almost every ride and stall, and so Ben decided that now was enough. He wasn't usually the type of person to make decisions like this (he'd just leave it up to whoever else was actually in charge), but this time, he'd put his foot down. He would, absolutely.
"Em, I'm getting tired."
"Are you serious? We haven't even eaten any churros yet!" Emily smiled devilishly at him while tugging on his blue sweatshirt's right sleeve. She was staring at him, as if trying to extract the information directly from his eyes. Her blonde locks were flowing over her brow and almost into her eyes, but that didn't seem to bother her. He guessed that, after a while, you'd probably get used to it, like you do with seeing your own nose. He really liked seeing her nose, though.
"You *love* churros," she pointed out with an overly exaggerated *o* in *love*.
*In love*, he thought. This time, he was going to put his foot down, right? That's what he'd decided. He didn't want people to control him anymore. But was that really what Emily was doing here? No. She was his best friend, she knew what was good for him. Right? She was looking at him, in obvious anticipation of his answer. He did really like churros. But did he love them? He wasn't sure about that. Okay, he did love them, but did he love them enough to warrant another thirty minutes at this godforsaken carnival?
He loved Emily, that was for certain.
Wait, what? Had he really just thought that? No, he couldn't have. That was just one of those jokes his brain made up sometimes. Amusing, albeit slightly infuriating. *Fuck off*, he said to himself.
"What?", Emily replied, clearly confused as to what he was saying. He wasn't saying anything though, right? What had he said?
"I'm, the," he mumbled, trying to make sense of what Emily could've just heard him say. Maybe she didn't hear anything, maybe it was just the wind. What could he have said to her? Surely he didn't say the love part out loud, right?
He stared at her face. Her brown eyes were intensely focusing on him, her lips slightly curled into a smile. She tugged on his sweatshirt one last time.
"So, churros?" Oh Thank God.
"*Fine.*"
***
As they were eating their churros, Ben couldn't help but notice how much chocolate sauce Emily had on hers with every bite. They'd ordered a little square box filled with twelve or so standing churros, along with a small tub of chocolate sauce that tasted criminally close to Nutella without actually being Nutella.
"You know this sauce is for dipping, right?", he said to her with a grin before taking another huge bite. "It's not supposed to be a full-on coating."
"What do you know, huh? Where does it say that?" She tutted and shoved her churro into the tub of sauce with ostentation. While pulling out the excessively coated pastry, she got ready to put her other hand under it as to prevent any of the chocolate sauce from dripping down onto her dress.
"Try it!", she said with an impish smile and held the dripping mess right up to Ben's mouth.
"No, I don't—" He tried to interject, but Emily had already rammed the chocolate-covered churro directly into his face. He resisted opening his mouth, so the chocolate sauce smeared all over this face and chin and then continued to drip onto his sweatshirt in big, brown globs. "Damn it, Em."
"I'm sorry, but why didn't you open your mouth?", she asked jokingly while holding back laughter.
"Fuck," he said angrily and fumbled around his pockets, trying to find a tissue or something. In the scramble, the smells of the chocolate on his face and the odor of bubblegum ice cream in the air mixed to create something awfully unpleasant in his nose. He was trying really hard to find a tissue now.
"Damn it, Em, why do you always have to do that?"
"Oh, *come on*, that isn't fair! I was just trying to have fun!"
"Fun? You know I hate these kinds of things. I hate *people*!"
She tutted. "Lighten up a bit, Ben!"
"Lighten the *fuck* up?" His brain was unfolding again, this time like floodgates, unable to be stopped by the water pushing against them. "You don't understand me at all!"
He was yelling now, and some people around them were beginning to notice. Fuck. He finally looked over at Emily, his face still covered in chocolate sauce, but she wasn't smiling anymore. Instead, she had this look on her face that she'd sometimes get. When watching a sad documentary, or when her mom prepared food that she didn't particularly enjoy. It wasn't a frown, it was more of a neutral expression of… disapproval? Disgust?
"Why is everything such a big fucking deal to you, Ben?", she yelled, tears starting to escape from her half-shut, pained eyes.
That was the moment a switch in his brain finally flicked. A switch that was probably in a back room, guarded by multiple doors with multiple, separate keys. Use only in emergencies, a sign somewhere close to the switch probably said. Don't use even, maybe. Ben tried to force his eyes shut with extreme determination. No, he wouldn't cry.
But he did. Tears started running down his face like waterfalls, and he tried to cover them up and wipe them away with the sleeve of his sweatshirt. He wiped his cheeks with the sleeves of his blue sweatshirt, and large amounts of chocolate sauce transferred from his face to the sleeves. God fucking damn it, he muttered to himself. The world around him was turning into a very small part of his vision, the rest filled with blurry streams of tears. He tried to get up from the bench they were sitting on and stumbled around the square where the festival was happening. He was sure he'd bumped into a few people on the way, but couldn't tell for absolute certain. After what felt like an hour and a half, he finally arrived at a tree a few meters away from the fountain square.
He rested one hand against the bark and tried to catch his breath. The thought of bubblegum ice cream was fresh in his mind again, the thought of the red, rickety swing set was morphing and mixing with the thought of churros, the thought of chocolate sauce in his face. The thought of Emily's smile, the thought of her frown, the thought of her sad movie face. Fuck.
He tried telling himself to breathe. *Five in, six hold, seven out.* That's what his therapist had told him. *One, two, three.* What's the worst that could realistically happen?, that's what he was supposed to ask himself. *Four, five.* Well. *One, two, three.* Emily might never talk to him again. *Four, five, six.* She might hate him, now that she'd just tried to be cute by shoving a churro into his face, and now that he'd reacted like a total fucking asshole. *One, two.* Was he already breathing in again? *Fuck.* He kept telling his body to calm down, his mind to stop racing. His heart to stop racing. He started leaning against the bark and then slowly slid down the tree trunk, sitting down on the ground with his knees close to his chest. He observed the rickety swing set in the middle of the field, and saw himself and Emily swinging back and forth again, talking about three hundred and sixty degree swings. He saw himself, laughing and giggling and occasionally licking a cone of
bubble gum soft serve he had in his left hand. He saw Emily doing the same. He saw himself wobbling a bit on the swing, trying to hold himself steady with just his right hand. He tried to breathe again. *One, two, three.* He saw himself, still holding the cone, sliding out of the swing's seat and yelling something. He saw himself landing in the rough, wet sand, the cone having left his hand and landing face-down in the sand next to him. *Four, five.* Tears were still running down his face. He saw himself turning around as Emily jumped off of her swing and came to his rescue.
"Are you alright?", he heard her say in her beautiful, melodic voice. *One, two, three.*
"I would've been, if you hadn't forced me to get this stupid ice cream!", he heard himself shoot back aggressively.
He felt something tap on his shoulder again.
"Are you alright?", he heard someone say. "I'm sorry, I shouldn't have said that."
It was Emily, standing next to him, bending down a bit to reach his shoulders. She slid down the tree trunk next to him. "I'm really sorry."
Ben tried to catch his breath, but with Emily here, it felt much easier all of a sudden. It's like a tension had been lifted from him, like someone had come to push on the floodgates from the other side. "It's alright. I'm sorry I yelled, too."
Emily tutted.
"You know, sometimes I think of that day on the swings."
"What day?"
"The day we had our first fight. Don't you remember?"
Emily tutted again. She placed her hand back on his shoulder, which he liked, because it was Emily's hand. That's all that mattered to him.
He wiped the remaining tears and chocolate sauce off of his face, his sweatshirt's sleeves now completely covered in both. "I keep remembering that day, being scared that I'll re-live it. That we'll have another fight. That you'll leave."
"*Leave*? Come on."
"I'm serious."
"I am, too. I'm not leaving."
She scooched closer and rested her head on his shoulder. "You know, I think about stuff too."
"Like what?", he asked while also resting his head on her head, which made him feel really close to her. Intertwined, even.
"You", she said quietly and grunted. "I really like hanging out with you, you know?"
"I do too," he said almost immediately. Did this mean what he thought it meant?
"Do you want to go home? I'm sure my mom has cookies."
He nodded. Maybe it did.

View file

@ -0,0 +1,148 @@
---
layout: blog
title: 👫 Emily's Fake Boyfriend
description: Emily's aunt keeps going on about her love life. To solve this problem, she pretends to be in a relationship with her best friend, Ben, who'd much rather have a real relationship with her. How will an evening with the three of them at the same table play out?
tags: [Short Stories]
book: true
discuss: https://twitter.com/Ellpeck/status/1378014977999659016
---
# Chapter 1
For Emily, Saturdays always seemed to be the days when most things went wrong. She didn't exactly know why, but she was almost certain that there was some pattern, some sort of spiteful spirit that hated Saturdays, hated her, or both. This Saturday would be one of the most bizarre ones so far, but she didn't know that yet.
The alarm rang, its screeching noises filling Emily's bedroom. With a huge sigh, she pulled the cover off of her body and jumped out of bed. As she picked up her phone from the nightstand, she sighed again. *Oh God*, she thought. *What now?* She tapped various locations of the device's large screen until she ended up in a text message chain with her mom, her brother and her younger sister. Her eyes kept trying to entice her to fall back asleep while she was scanning the lines of the last ten or so messages as carefully as she could.
*Damn it*, she thought as she got to the last message. *That's today*? She quickly gathered her things, put on her favorite, pastel pink cardigan, messily fluffed up her hair as she ran past her large mirror and made her way downstairs.
"Good morning!" Her mom said with a slight smile. She was standing in the kitchen, as she usually did, preparing homemade waffles. Emily's mother was a very good cook and baker, but waffles weren't her strong suit, for some reason. Every time she tried (and she tried too much), they'd end up burnt or somehow taste of licorice. The thought made Emily grimace as she walked past the kitchen.
"Morning, mom!" she hastily said and made her way to the front door.
"Not so fast," her mom shouted back from the kitchen. "Don't forget your waffles!"
*Oh, God.* "Of course," Emily replied with a sigh.
***
Ben was sitting on the side of his bed with his laptop firmly planted on his crossed legs. With the speed of a twenty-fingered person, he was typing away on an essay for school. He heard a knock on his room door and, while *very* annoyed by this, he still managed to calmly shout "Come in!"
"Emily's here," his mom said while peeking her head through the slightly opened door. "She says it's important."
Almost immediately, far too many thoughts started sprinting through his head: It's *important*? What could that possibly mean? Was Em, his best friend since kindergarten, *dying*? No, that's unlikely. But what if she *was*? Why wouldn't she just *call*? Oh God, no, a call would be *even worse*. Still begging his brain to shut up, he trotted down the stairs and arrived in the house's main hallway. A myriad of family photos plastered the walls, most of them from when Ben was still a child. His mom *loved* showing off pictures of his "little baby boy," evidently in the most embarrassing ways possible.
"Ben!" Emily said hastily as he opened the door. "I need you to be my boyfriend!"
*His what*? His mind began racing again. Not only was that a very weird way to phrase that sort of thing, but it was also an extremely unexpected request in general. Emily, while insanely beautiful and extremely charming, wasn't really known to start relationships with anyone. Ben occasionally talked to her about it. It didn't seem to be her favorite topic, so usually, he'd lay off after only two or three questions. They weren't necessarily the easiest of questions for him to ask her, either, because he was *obsessed* with her, almost uncomfortably so. Not in a stalker-y, watch-her-every-night, masturbate-to-her-daily sort of way, but in an I-love-you-and-I-want-to-marry-you kind of way. Of course, Ben (being who he is) was far too self-conscious to *ever* bring it up.
"You need to *what*?", he responded quickly.
"Well," she said and took a deep breath. "My aunt is coming to town and I may or may not have told her that we're, uh, together."
"You did *what*?"
"I know, I know. It's bad." She tutted. "The thing is that my aunt can be really *annoying*. She always goes on and on about my love life, about how I haven't found a boyfriend yet, even though I'm already 19, and how that is *absolutely unacceptable*. So, I decided to put a stop to it."
Ben closed his eyes. *Fuck.* Of course, it wasn't what he'd hoped it was. *Of course*, she didn't want to be his *actual* girlfriend. Who'd want to be? Nobody. Especially not her.
She was standing there, her blonde locks swaying back and forth in the breeze that the open front door let in, and Ben was lost for words.
She tutted again. "Aren't you going to say anything?"
"Well," he started. How could he say yes to this? If anything, wouldn't it just cause him more pain to have an entirely fake relationship than none at all? Maybe. But maybe, this could lead to something. Maybe, he thought, this could be like those cheesy romance films that he sometimes watched when he was in a *particular* mood. Maybe this could really be the start of something.
"What do you need me to do?" He asked.
***
Emily breathed a huge sigh of relief. "Let's sit down somewhere," she said quietly.
As she finally entered the house, and they started making their way over to Ben's living room, she felt a kind of unease course through her body. It wasn't the *bad Saturday waffles* kind. She couldn't quite put her finger on what this feeling was, but she felt her heart beating faster when they sat down on a rickety, brown sofa.
She knew, of course, that Ben didn't enjoy this whole situation. She knew that he wasn't the best liar, and she knew that he didn't *enjoy* lying all that much either. But this was different, she thought to herself. Emily never understood what, if anything, Ben felt for her. It wasn't that he was secretive about his feelings, it's that she was incredibly bad at reading them. To her, it was weird that so many people automatically *assumed* that she was amazing at talking to people, making conversation, and understanding what other people want. But this wasn't really the case. While Emily *did* love hanging out with people, the part of her brain that was meant for understanding people's feelings was probably being repurposed to store excessive amounts of Ariana Grande quotes. And it made her feel bad, too, because her best friend Ben was absolutely *packed* with feelings.
While poking at a small hole in the brown fabric she was sitting on, she recalled a situation from a few months ago. She and Ben were in the park, and there was some sort of spring festival going on. She had dragged him out to it because, even though she didn't necessarily *like* taking this role in his life, she'd often try to take him out of his shell and help him fight his anxiety. During that festival, he had a full-on *panic attack* (the kind of thing that she, as an avid Ariana Grande listener, did not understand), which caused him to run off and hide.
Was this really such a good idea?
"Okay, so," she said and let out a big sigh. "Let's talk relationship. What kind of boyfriend do you want to be?"
"I'm sorry?" He said, flustered.
"Well, I didn't tell my aunt that many lies about you, so the whole thing is pretty open. You can be super caring, or, you know, the bad boy type."
"The *bad boy type*?"
"Yea, like, uncaring and aloof, you know? Maybe wear a leather jacket."
"A leather- have you *met me*?" He responded in playful disgust.
"Okay, super caring it is, then," she laughed and moved her hands around in the air as if writing something down on a giant, invisible notebook.
Honestly, *super caring* was exactly the kind of boyfriend that she actually wanted. Of course, she would never say this out loud, but occasionally she dreamed of the perfect storybook romance. *Romeo and Juliet*, just without all the tragic stuff. Just like that one Taylor Swift song. While she'd fooled around with some of her girlfriends once or twice, she'd never actually had a relationship with anyone, especially not a guy. She didn't really know why, either. She briefly wondered if any guys had ever been visibly into her. Maybe she'd just missed it.
She'd also never admit *this*, of course, but Ben seemed like a fairly good candidate for the position of the super caring boyfriend. He was innocent, sweet, and he had the mane of an extremely gentle lion.
Uncomfortable silence had set in between the two, as it sometimes seemed to do. It didn't help that Emily had one of those weird thoughts again. Very occasionally, she'd have this intrusive image in her head: Ben, lion-like as he looked, not-so-gently on top of her. Sometimes he'd touch her breasts; sometimes they'd be kissing. Was she sexualizing her friendship? Maybe. Was that bad? She didn't know. She also didn't know if she cared yet. After all, she didn't *mean to*. It just kind of happened every now and again. If anything, didn't that speak to her fondness of Ben?
In an attempt to quickly change the subject on her mind, she started telling a story about her aunt.
"You're going to *love* my aunt," she said sarcastically. He looked at her with a sense of dread in his eyes. "Okay, one time, we were at this restaurant, the whole family all packed into this corner booth, and she would not stop pestering the poor waiter with question after question about the most ridiculous stuff! Every time she waved him back over to our table, he looked more and more tired of her shit."
"That's actually kind of horrifying," he responded and shook his head slowly.
"Right?"
# Chapter 2
In the early evening, Ben found himself sitting at a big dinner table, straight across from an overly cheerful looking lady. If he had to guess, Em's aunt was probably around fifty-five years old, maybe a little older. She was slightly hunched over the table, resting one of her arms on it. *That's not how you sit at a table, young man*, he heard his own aunt's voice echoing in his head. Clearly, Emily's aunt didn't care, and from what he'd been told, this wasn't surprising.
When Emily's mom came into the room from the kitchen, she was carrying a large tray that held a variety of sweet pastries and cookies. Homemade, Ben assumed, waiting to bite into one of those chocolate croissants. Emily's mom set the tray down in the center of the table and pointed at it with an open hand as if to say *There you go, dig in*. So Emily and her sister did. After they finished selecting something they liked, Ben also felt comfortable taking a croissant.
A chocolate cookie in her delicate fingers, Emily's aunt turned to Emily. "So," she began while raising her thickly overdrawn, black eyebrows. "How have you been, my love?"
"Well, school's been going pretty well. I've only gotten good grades this semester, and my classes have been bearable."
Her aunt started smiling wide. "A perfect student as always! I expected nothing less of you, dear."
Emily provocatively rolled her eyes and sighed. Her aunt didn't seem affected by that at all. Instead, she continued smiling as she bit into her cookie. "And you, love?"
Confused for a second, Ben realized that she was talking to him now.
"Me? Oh, well," he stuttered. "School is also going great for me. Emily and I are pretty much on the same level in terms of grades and stuff."
"On the same level, you say? Maybe that's why you two get along so well! For years now, I've been *absolutely positive* that you two would make the cutest couple. And now it's *finally* happened, God bless."
Emily sighed, louder this time. Ben let out a stifled chuckle. *I wish*, he thought to himself.
***
As the conversation continued over dinner, followed by coffee, Ben felt himself zoning out from time to time. As he continued to observe Em and her aunt conversing, he felt his mind wander to a different world without his control.
A world where him and Em really *were* a couple. A world where, against all odds, they'd happily been together for years, with their own apartment, with floor-length windows and a balcony. They'd sit outside in the evenings, eating ice cream and sipping on cocktails. They would laugh and share stories of their day while watching the sunset over the park. They'd go into town on the weekends and eat at that little Italian place he keeps wanting to go to. They'd never fight anymore, because they'd be happy. He'd never cry alone, never wonder why he was too *stupid* to tell her how he felt.
Emily grabbed his hand. "We're going upstairs now," she said and got up from her chair.
Even though he knew it was fake; even though he knew that her hand in his meant *nothing*, it felt good. He craved contact like this, with someone he really cared about. He'd hug Emily more if it wasn't for his fear of being overbearing. Of being a nuisance. Why did he have to feel that way?
Emily dragged Ben behind her as she walked up the stairs, her palm still resting in his. When they got to the door of her room, she still hadn't let go of his hand. Why? Was Ben just reading into things again, or did she *enjoy* holding his hand? *Surely not. She'd never enjoy that*, he told himself. Then, as he frequently did, he told himself to stop telling himself things like that.
Emily finally let go of his hand upon entering her room. This was one of Ben's favorite places to be. Sometimes, he'd imagine him and Em cuddled up together on her bed, sharing a blanket, watching a movie or playing *GTA*.
He thought about this kind of thing a lot. Of course, he also thought a lot about the fact that he thought about this kind of thing so frequently. Was it bad to have these thoughts? Was he risking ruining his perfect friendship with Emily by making some big romance out of it in his head? Was it unfair to her to keep quiet about it? As they sat down next to each other on the bed, he tried to let these thoughts fade.
"Thank you *so damn much*, man," Emily said and let out a sigh of relief. "What a nightmare."
"Oh come on, it wasn't *that* bad."
"Did you hear what she *said*?" Emily put on a very bad, fake British accent. "I was *absolutely positive*," she mocked. "*My dear.*"
Ben couldn't help but laugh. "She's not that bad! I thought she was nice."
"Oh shut up, I *know* you." Did she? "People like her drive you *mad*! You never miss out on an opportunity to hide in your room. You don't *like* her."
Maybe she did know him. And maybe it was this realization that made him unable to think about the words flying out of his mouth. He let out a quiet, but emotional sigh, and said "I like *you*, though."
Emily turned and looked into his eyes. As he stared back, he could see a tiny reflection of his face in her brown irises. "I like you too, Ben. You know that," she said.
Maybe she didn't know him after all. He focused intensely on her face. What if this was the moment to be honest? What if, in the light of this fake relationship, he could finally be true to how he felt? Anxiety boiled up inside him. This was a panic attack just waiting to happen. But he had to try. Right?
"No," he said and gently took her hand. "I *really* like you, Em."

View file

@ -0,0 +1,172 @@
---
layout: blog
title: 📅 Jed's Things to Avoid in Life
description: "Jed's rules are clear: No relationships. They interfere too much with his life, and he's not made for them anyway. But then, he sees his old crush from school in his hometown. Will he break his own rules for Davy?"
tags: [Short Stories]
book: true
mature: true
reedsy: https://blog.reedsy.com/creative-writing-prompts/contests/90/submissions/63882/
discuss: https://twitter.com/Ellpeck/status/1385686796911185920
---
Jed didn't do relationships. That was just one of his rules. Actually, it had become such an important rule to him that he added it to his very official, *highly* scientific List of Things to Avoid in Life. They were too complicated, and the touchy-feely stuff usually left him feeling inadequate about his ability to be there for someone. He also just didn't have the time, with his fifty-hour work week and all that. So not wasting his time trying to care for another person, especially considering that they'd probably notice pretty quickly how little he was able to care for them and ultimately leave him to be on his own again. So why bother, right?
He was sitting on a crowded, noisy train on his way to visit his parents in his childhood hometown. He didn't necessarily enjoy going there again, but this weekend was his mom's birthday, and he loved her too much to skip it. Even though his grandma would also be there, who he had a particular grudge with after finding out a few years ago about her dislike of a *pretty important* part of his life. So he wasn't particularly looking forward to talking to her about "the girls she envisions him marrying."
He looked out the window, the lush, pine-plastered mountains in the distance slowly creeping by, and imagined what it would be like if he really *was* as much into girls as his grandma wished he was. It's not like he hadn't thought about it. Of course, it would be easier for him to be straight. In fact, maybe his rules about relationships were only so airtight because he was afraid of being bullied. He didn't enjoy the thought of holding hands with his boyfriend, only to be attacked by some idiot in an alley. But his other reasons were good, too, right? He *was* a little emotionally unavailable at times. He *did* have that horrible work week. *Yes*, he thought to himself. *Those are good enough reasons to avoid the perfect, storybook romance you dreamed of as a kid, damn it.*
After leaving the train, he stopped in the middle of the platform and looked around to get his bearings again. Out of the corner of his eye, he noticed something odd. He turned to face the front window of a small bakery, the glass protecting delicious looking pastries and cakes that were quietly sitting in a large vitrine. But that wasn't the odd thing.
He started closely inspecting the head of the person standing behind the register, currently handing a glazed donut to an old lazy. After a few seconds, the person turned in his direction, and started looking directly at him. And there he was, standing in the middle of a train station, locking eyes with the *last* person he wanted to see in this town. He smiled and waved awkwardly, and the person behind the register did the same, before turning back to the old lady and finishing their transaction.
Jed, clearly having lost his mind, thought it would only be right to go inside and say hello. A little bell that was mounted behind the door rang. Now, he was standing right in front of him. His long, wavy brown hair had the same elegance as it did the last time Jed saw him, as if nothing had changed. Davy must've been sat in a time capsule for the last four years, because he still looked exactly as beautiful as he did all those years ago at school. No, better. Davy had broad shoulders and a wide frame. His arms were thick and muscular and Jed imagined them again, wrapped around his body, warming him. Protecting him.
"Jed! I didn't know you still lived in town," he said with a smile that penetrated all of Jed's defenses.
"I don't, actually," he stuttered. "I'm visiting my parents."
"Ah, that makes sense. Train station." Davy laughed awkwardly and pushed the hair out of his face with his hand. His motions were smooth and soft, and Jed couldn't help but notice the veins in his arms twitching as he moved his hands. *Jesus Christ*, he said to himself. *Get a grip.*
"How have you been?" Davy asked and raised an eyebrow.
"I've been pretty good, actually," Jed mumbled, still mesmerized by Davy's movements. The grip clearly hadn't been gotten yet. "I'm working at a pretty big software company."
"That's awesome," Davy said with a very real, supportive air of enthusiasm.
"And you?"
"Been working here to help with uni. That shit's really expensive, man." Davy chuckled.
Jed nodded his head slightly. "Yea, I can imagine," he said.
"Anyway, it's cool that you're here. Do you maybe want to catch up some time?"
Jed froze. *Oh my God*, he thought. Had Davy really just asked him on a date? Okay, well. Not a *proper* date, but...
"Uh, yea, that sounds great," he mumbled. If Davy was a mirror, he'd probably see his entire face turn into a tomato right now. *Oh, God.* "I'm free the whole weekend," he continued.
They exchanged phone numbers, and Jed breathed a sigh of relief when he exited the bakery. Thankfully, Davy didn't seem to have noticed how awkwardly Jed was acting around him. Was he even acting that awkwardly? Possibly. Probably.
***
Only a few days later, Jed arrived at a coffee shop that he used to go to with his friends after school sometimes. It was small, filled with small nooks with comfortable looking armchairs that had colorful blankets thrown over them. Taking in the romance that this place clearly emitted, he sighed to himself. *Oh God, this is a* proper *date, isn't it?*
He waited in the entryway of the café until Davy arrived. And *boy*, did he arrive. He walked into the room wearing a jean jacket over a black logo tee and jeans. His hair was blowing in the wind let in by the open door, and it reminded Jed of their first encounter at the train station. The whole outfit made Davy seem strong. For a second, Jed imagined what it would be like to be scooped up by his strong, muscular arms like a princess. Like a *prince*.
*Get a grip*, he thought to himself.
"Hey man," Davy said with a smile as he saw Jed. *Hey, 'man'?*, he repeated in his head.
"Hey Davy," said Jed with an awkward half-smile. He tried to wipe it off. "You look," he started, but he didn't know how to finish.
"Gay?" Davy suggested. "I know. I just love this jacket so much."
"That's not-", Jed stuttered. "I didn't ... you look *good*." He hesitated. Why did he *say* that?
"Thanks. You don't look too bad yourself," Davy said and started making his way towards one of the seating nooks. He brushed Jed's arm with his own while passing him, and it made that place tingle with... with something.
Jed realized that he didn't even know for sure if Davy was into guys. He wondered for a second if that comment about his jacket was confirmation that he was, in fact, gay. Was anything he'd done so far confirmation that he was?
A weird feeling of unease creeped up in Jed's chest, and it immediately entered his heart. *What if he's not even into guys?*, he thought to himself. He didn't do relationships anyway, though, so why would it matter -- right? They were too complicated, too time-consuming, and too draining of the small amount of emotions that Jed had. So it was of no value to know whether Davy was into him or not.
He really enjoyed the idea though. As he watched Davy sit down on a sofa in one of the nooks, he imagined what it'd be like to be on that sofa with him. Sitting next to him. Close. Kissing him. Davy's hand moving closer to Jed's--
"Are you coming?", Davy called and waved. *Oh God*, Jed thought to himself. *Hopefully not.*
Jed started walking towards him, and Davy started patting the spot next to him on the sofa. He wanted Jed to sit next to him. *Great.*
Of course, being the gentleman he was, it was only right of Jed to actually go ahead and sit down next to Davy. That was the only reason, *of course*.
They both ordered drinks (hot chocolate) and food (cheesecake and apple pie) and started chatting about how they'd been since school. Davy talked about his university courses and the bakery job and how he'd sometimes see his old friends at the train station, and Jed was *fascinated*. It wasn't that Davy's stories were actually *fascinating*, but somehow, because *he* was the one telling them, they seemed more alive than Jed ever thought a story could be.
When Jed started talking about his job, about the long work hours and about his family, Davy turned a bit to face him. Now they were sitting closer, almost opposite one another, and Davy's gaze seemed to be locked onto Jed's eyes while he told his stories. He'd never met someone, Jed thought to himself, who seemed so genuinely interested in his life. Somehow, the two of them were just fascinated with each other, it seemed. But Jed's rule was clear: *No relationships*. They were a waste of time, and that was a final, reasonable, and educated decision.
That was, of course, until they started talking about Jed's homophobic grandma. As soon as he mentioned that his grandma didn't 'approve' of his sexuality, Davy's smile faded, and he rolled his eyes. The sight made Jed chuckle.
"Relatable?", Jed asked with a tiny grin.
Davy tutted. "Oh God, don't get me *started*."
He shifted a little on the sofa, and they were sitting even closer to each other now. Was he doing this on purpose?
"My mom's the same way."
"Your *mom*? That's terrible!", Jed hissed.
"Eh, I've been getting by alright. I don't need her, you know? It's her loss." Davy began smiling again. "I've got enough people in my life who support me."
"That's good," Jed responded. Overwhelmed with the flood of endearing sentiments rushing through his chest -- the homophobic mom, the sweet sentiment about Davy's chosen family, and just how close to each other they were sitting right now -- Jed couldn't stop himself from saying more. "I'd support you too, you know."
"You know what," Davy began. *Oh God*, Jed thought to himself. This was it. He'd ruined it. *You're getting a bit creepy*, that's what Davy was about to say. Right? He'd said too much. But Davy's smile didn't fade. Instead, he chuckled and put his hand on Jed's shoulder.
"You're great to hang out with, Jed," Davy continued. "But I really have to go now, sadly. I really want to see you again, though."
Jed's cheeks warmed up, and he began to smile again. "Me, too," he said shakily.
Over the next few days, Jed kept daydreaming about Davy. His cute smile, his wavy hair and his muscular, strong arms. He kept picturing, over and over, how Davy grabbed his arm when they sat so close in the café. He should've kissed him then and there. No, it would've been too soon. Plus, they had another date lined up. Because he hadn't seen town in a while, Davy came up with the idea of hiking to the top of the nearby mountain and looking down at the cityscape from there.
But maybe he shouldn't get closer to Davy, he thought. He didn't want to give him the wrong idea. His list of rules was very strict, and Jed intended to continue following it. No relationships, because they are too complicated and time-consuming. But Davy could've happily consumed all of his time without Jed even remembering that his list of rules *exists*.
***
There he was. Strong as always, wearing a t-shirt that made his muscular upper arms even more pronounced than usual. Today, his long, silky hair wasn't flowing over his shoulders, though. He had tied it up into a ponytail, presumably so it wouldn't bother him during their hike.
Jed approached him on the parking lot close to the hike trail leading up the mountain, and Davy turned around slowly.
"Hey," he said in a rough voice. He cleared his throat, before saying "hey" again, less raspy this time.
"Hi. I'm excited!" Jed blurted out with an unusual amount of confidence. *The rules, damn it*, he thought to himself. *Get a grip.*
"Me too. I packed some snacks for us, as well," said Davy and pointed to his backpack. He shifted his upper body and laughed, causing what sounded to be loose food items to rustle in the bag.
Halfway up the trail, Jed started getting exhausted. It wasn't that he was out of shape (he'd been working out occasionally because of the part of his List of Things to Avoid in Life that read *getting out of shape*, of course), but something about this hike was different to him. Stopping on the trail, he grabbed a bottle of water out of his backpack and started gulping it down without hesitation. Davy stopped as well, and observed Jed as he almost finished the entire bottle.
"Want to take a break?", he asked slyly.
"Shut up, I can do this!", Jed blurted out without much thought. *Oops.* "I'm just-"
Davy cut him off. "Well, I know you can, but you can also relax with me for a little while."
Relaxing sounded like a great idea to Jed. He wiped his mouth and chin with his forearm and looked over at Davy, who had already found a nice bit of grass to sit down on. *Laying down on the grass together*, he thought, remembering this very fantasy having been stuck in his head for the last few days. *Of course. Damn it.*
Jed walked up to Davy and looked down questioningly. Davy looked back up at him and smiled.
"Come sit," he said with a grin, pointing to the ground right next to him. Jed did as he was told.
And now, there they were, just like in Jed's *stupid* fantasies. Sitting next to each other, looking out over the forest and a small part of town, with Jed feeling the cool, green grass underneath him.
"You know," Davy began. "I used to think that you were kind of weird. In school, you know. I only realized afterwards that..." He hesitated. Jed turned his head to see the expression on Davy's face, but there wasn't much being expressed there at all. Eventually, Davy continued.
"I think I realized that I had the tiniest bit of a crush on you."
On... *what*? Jed's heart skipped a beat. What had Davy just said? Clearly, Davy was out of his mind. Or Jed was. Was he dreaming? Was he passed out from exhaustion somewhere, laying on the path, with Davy wondering what he'd gotten himself into? Jed shook his head. *No.* This seemed to real to be a dream.
"You... you did?", Jed stuttered. Davy chuckled.
"I think so, yea. You were always kind of cute, and seeing you at the train station the other day... I don't know, it felt like a sign."
*A sign*, Jed thought to himself. He could kind of see that. The hunky, adorable student who worked in a bakery, with his long hippy-like hair, believing in *signs* and probably *soulmates* and what have you. This made sense to Jed. But the fact that Davy was saying all of this to *him* made no sense at all.
"Are you serious? I--", Jed paused, trying to formulate a coherent sentence in his mind. Davy was looking at him with a sense of desperation in his eyes. *Tell me that you like me too*, they seemed to say.
At this point, Jed went over the options in his mind. The rules, the meeting at the train station, the *coincidence* of it all. Maybe he believed in signs, too. Maybe he believed that, out of all the people he could've met that day, Davy was just the perfect one. Maybe, he thought to himself, his rules didn't actually matter that much after all.
"I actually really liked you too. In school. I always saw you with all of those girls and I thought, you know. I thought I'd never have a chance. Especially since you seemed so..." Davy cut him off.
"So straight?", he said and laughed.
"I guess, yea."
"Well, I'm not. I mean, I am. I'm bi, actually," he explained.
"That makes sense," Jed responded, not really sure what else to say. They were both silent for a moment, but their gazes were still interlocked. They were sitting close enough together now for Jed to feel both of their breathing warming up the air between them.
"Do you want to..." Davy began, but he paused. Jed wanted a lot of things right now. He imagined moving closer to Davy, putting his hand on his thigh, maybe lower, deeper, and going through Davy's hair with his hand. He didn't, however, know what Davy wanted. So he didn't do anything.
"Can I kiss you?", Davy asked after an uncomfortably long silence. Jed didn't answer with words -- how could he possibly answer that question without melting into a puddle then and there -- but apparently, his wide smile was enough indication to Davy that Jed *did* want to kiss him.
Davy moved in closer, grasping Jed's hip with one hand, and as Davy leaned in closer with his head, Jed could see the veins in his arm twitching as he moved.
Jed closed his eyes, and he felt Davy's lips touch his own. He felt him reaching around Jed's body with his other hand, fully enclosing him in his strong embrace now. It felt good. Jed wanted to continue, forever, staying here and kissing Davy until the end of time.
This was good. To Jed, it was the perfect place to be.

View file

@ -0,0 +1,69 @@
---
layout: blog
title: 🐶 Dog-Earing and Other Terrible Reading Habits
description: Since I've been reading a lot more in recent history, I thought I'd start writing some blog posts about the reading. So first up, here are some of my terrible reading habits that would probably make a lot of book lovers angry. This isn't clickbait. I'm just terrible.
tags: [Reading]
discuss: https://twitter.com/Ellpeck/status/1408565191218978818
---
So recently, I've been reading a lot more again. Since I got my first Kindle [and talked about it](https://ellpeck.de/blog/reading) a while ago, I have now also started tracking my reading progress on [the StoryGraph](https://app.thestorygraph.com/profile/ellpeck). I also started watching some more book- and reading-related YouTube videos, and I came across this tag that I thought would be *perfect* for me to talk about for one specific reason, which I'll get to shortly.
To sum it up: My boyfriend thinks dog-earing books (that you own!) is the worst, and he despises me for doing it. But I want all of you to *hear my case* about this. So read on. Please.
# Do you have a certain place at home for reading?
A while ago, I actually got one of those cheap-but-nice IKEA chairs that everyone seems to have and set it up as a little reading nook in a corner.
But then I realized that reading in bed, before going to sleep, actually works a lot better for me because I take sleeping pills, but they take a while to start working. So I got into the habit of taking them, and then reading for an hour or so in bed, and then putting the book away and dozing off right when I feel the medication starting to make me drowsy.
# Bookmark or random piece of paper?
Okay, hear me out: *Dog-earing*. Of course, if you get a library book or read a friend's book, this is not a nice thing to do, because, after all, it *is* damaging someone else's property. In that case, a random piece of paper is a pretty good way to go, in my opinion.
*But.*
A book that looks like it's been read, with the pages a little warped and the spine maybe, possibly a little broken, and the marks of dog ears on some pages looks *so much nicer* than a book that looks all new and fancy and clean.
Listen, okay? Dog ears tell a *story*. Not only do they show you where you (or other people) took a reading break, but they also show you which parts of the book might have been difficult or otherwise slow to read, because those parts would have a lot more dog ears than other parts of the book.
Okay, maybe this is just me defending something that is objectively terrible. I like it, though.
# Can you just stop reading or do you have to stop after a chapter/ a certain amount of pages?
I absolutely can't stop reading in the middle of a chapter. For me, it's not necessarily that I want to know how the chapter ends or that I enjoy stopping at the end of a chapter because it's a cliffhanger, and it'll make me feel excited to get back into the book later.
My real reason is a lot more boring, because it's just that... for some reason, it feels *wrong* stopping before the end of a chapter, similarly to how it feels wrong for some reason to stop watching a show in the middle of an episode.
Chapters were made for a reason, and the author probably thought about the pacing of the book and, as a result, the placement of the chapters. It doesn't feel like my place to just disrupt that pacing. And that placement.
# Do you eat or drink while reading?
I don't think I've ever eaten while reading. It seems... unfitting. Especially because there are so many foods that make your fingers greasy or smudgy or... crumby. I wouldn't want to get that on my precious book. Or Kindle.
I do sometimes drink while reading, though. Especially when I prepared a little place and time to read, like when it's a nice day out, and I set aside some time to read and get a sunburn I mean suntan. I'll usually get a big glass of something nice. Yea.
# Multitasking: Music or TV while reading?
I... do people do that? How? I don't have the mental capacity for that.
# One book at a time or several at once?
I really *want* to be reading several books at once. I like the idea of reading one fiction book and one non-fiction book and then choosing which one to read based on how science-y I feel on any particular day. But whenever I try to start another book while I'm already reading one, I get one of two feelings: *The other book is so much more fun to read* or *This one is so much better than the other one*, both of which just make me end up reading just a single book again.
# Reading at home or everywhere?
Since I mostly read on my Kindle, it would make a lot of sense for me to read in other, non-home places too. But I rarely do. Partly because, when focusing on something like a screen or a book for too long while riding a bus or a train makes me feel *incredibly* dizzy. But also partly because I read a lot of somewhat raunchy romance novels, talking about which, and showing the covers of which, in public sounds like *a time* for an introverted person like me.
# Reading out loud or silently in your head?
I didn't realize there were people that read out loud, to be honest. It sounds kind of fun. I should try that.
# Do you read ahead or even skip pages?
I used to read the end of a book right away when I was a little kid, because I was scared that the book would have a sad ending. Actually, I used to also *change* the endings of sad books by writing a nicer ending into the book to ease my mind. I was wholesome like that. I was also, like, 9. So ease off with the pitchforks.
Nowadays, I don't even really have the urge to find out what happens later on in the book. After all, the current part is already interesting, and I'll find out soon enough what happens in the future, I guess.
# Breaking the spine or keeping it like new?
I will *absolutely* break the spine of any book I read (if I own it), because otherwise books are just too V-shaped to hold comfortably. It's not my fault that books are as flexible as a rock, okay? Do some yoga or something.
# Do you write in your books?
Depends on the book. If it's a science-y book that I want to go back to, I'll definitely write notes and highlight sections in it. Since I mostly read on the Kindle, I'll actually highlight sections that I find funny or interesting to be able to get back to them later. Or to be able to share them with my boyfriend, who isn't *the least bit interested*.
# Conclusion
I think my reading habits can be summed up pretty accurately as "book murderer". Which I'm honestly fine with.
I don't do it to books that aren't mine, though. I'm not, like, a library... hater. Or anything. Libraries are cool.
Since I've been reading a lot more again in recent history, I also want to start writing more blog posts about my reading habits and the books I enjoy. If you're interested in that sort of thing, you can head back to [the post list](https://ellpeck.de/#blog-reading) to see other posts in the Reading category, and you can also subscribe to the blog's RSS feed there too. If anyone still does that. I think RSS is dead. Right?

View file

@ -0,0 +1,140 @@
---
layout: blog
title: 📦 Of Abby, Love and Butt-Shaped Pillows
description: "Jeremy had just moved into a new house when he found a mysterious, heart-shaped box in the attic. Intrigued by its contents, he started asking his neighbors about it, and he would soon discover a secret love story."
tags: [Short Stories]
book: true
reedsy: https://blog.reedsy.com/short-story/qtt69j/
discuss: https://twitter.com/Ellpeck/status/1494068545105416196
---
Jeremy was sitting in his kitchen as suddenly, the room went dark.
Great. Two months living here, and the house is already falling apart. That was just Jeremy's luck, wasn't it?
He got up from one of his brand-new kitchen chairs and started making his way up the stairs to the attic, which is where he'd probably stored the light bulbs he brought from his old apartment. He didn't actually remember, mind you, because Jeremy hardly ever remembered anything. Jeremy was a man of few words, and a man of few thoughts.
Now standing on the wobbly ladder that folded down from the ceiling, he used most of the strength his body allowed him to lift a thick wooden board out of the way to reveal a hole in the ceiling to the attic. He climbed the scary ladder all the way and breathed in the stale air that had accumulated over all of those years that the house had stood on its own. After shuffling around some boxes with various words on them (like "kitchen" and "bathroom" and "heavy shit"), he finally spotted a small, transparent plastic box that contained a few light bulbs. Crouching into the corner to retrieve them, he noticed something he hadn't seen the few times that he'd been up here: A small, heart-shaped box, almost hidden entirely by the layers and layers of dust that were covering it.
Ready to reveal an old family secret or a bomb or, more realistically, just some weed in a little bag, he shuffled closer to the box and reached out to grab it. It was dusty. And kind of disgustingly sticky. Why was it *sticky*?
***
Light bulb changed, box dusted and un-sticked with a sponge that he now *desperately* wanted to throw away, Jeremy was ready to open it and reveal the weed-covered family bombs inside. The box was made of a thick cardboard-y substance (that was the technical term, he was sure), and dusting it revealed that it had been pink all this time.
The box contained several pieces of paper, a tiny, butt-shaped pillow that was probably supposed to look like a heart, and a silver necklace that represented half of a heart. Presumably, it was one of those where another person wore another one that was the opposite half, and it was a metaphor for their undying love, or a metaphor for the fact that a lot of people get divorced. Jeremy was never really sure. All in all, it was a lot of heart-shaped, love-related items, though.
But who had the other half of the necklace?
Clearly, this box had been meant for someone else. Obviously, it was not meant for *Jeremy*, but he knew that. He thought that it must've been meant for someone other than the person that had put it in his attic. Which was probably the previous owner of the house. Reading them, Jeremy quickly realized that the pieces of paper were actually letters. Love letters to someone named *Barb*. Was that even a *name*? Maybe it was short for Barbara?
There was one letter that stood out to him in particular. Most of the other ones seemed like they were just kind of *in* the box without actually belonging there (they were short notes about making plans and meeting up and holidays), but there was one that was clearly about this very box.
*My beloved Barb,*
*Before you, I never met anyone who was quite as intoxicating, as filled with love, as passionate as you. I want you to know that, now and forever, I love you.*
*I did not tell you this before, because I was scared. I was worried that you would not feel the same about me. I was worried about so many things. But now that our paths are about to part, and now that we might never see each other again, I need to give you this.*
*It is all the things I could never give you. It is all the things I bought, and made, and wrote, and then stowed away instead of giving them to you. Because I was scared.*
*But I will not be scared this time.*
*I love you, Barb.*
Well. To say that Jeremy was moved by this letter was an understatement. Jeremy, as few words and thoughts as he had, he really had a lot of space in his heart. And clearly, Barb, whoever they were, had never received this box... right? In his head, he calculated how old this box could *possibly* be. None of the letters seemed to be dated, and none of the other items were food, so they didn't have expiration dates. The butt pillow had a little sign attached to it that was filled with washing instructions and small print, but also had the number 95 on it. Was it made in 1995? Maybe. Probably not, though. No one post-1995 was named Barbara, let alone *Barb*. And the previous owner had never mentioned a Barb, or a Barbara, or a barbarian, or anyone like that.
More importantly, what was he even going to do with this knowledge? And this box? Was he really going to spend his afternoon trying to figure out how to get it to its rightful recipient? And what were the ethics of doing that, anyway? What if this anonymous romantic had already moved on from Barb, probably to a person with a human name?
No, he was determined now. Jeremy loved love. He'd have done anything for love. Even chase down a person that might not exist based on some information from a sticky pink box.
One thing he knew for sure was the house's previous owner's name: Mariland Smith. Also not a human name, he noted. Maybe Mariland knew something about this box of butts and secrets.
Mariland was easy enough to reach, given he'd already talked to them a few times about the bathrooms and the piping and the power bills and the keys and how to get up to the attic. Weird then that they'd never mentioned a mystery box up there, though.
"A Barb, you say? Hmm," Mariland began after Jeremy told the story. Of course, Mariland didn't point out how peculiar the name Barb was because they had an equally ridiculous name. Instead, they pointed out that they'd rarely used the attic and that that was probably the reason they never saw the package. Made sense, Jeremy guessed.
After some silence, Mariland gasped.
"Actually, one of the neighbors did talk about a Barb from time to time. I think it was Mr. Garcia just down the road. You know, honey, I'm really sorry, but I didn't really know many of the people there."
Jeremy smiled. That was surprisingly helpful. "Thank you so much, that is honestly really helpful," he said earnestly, and after some goodbye-ing of the regular sort, he hung up.
***
Mr. Garcia, while being very old and exceptionally grumpy, actually had quite a bit of useful information for Jeremy. It wasn't the wild goose chase he feared to find out that Barb was, amazingly, short for *Abigail*, which seemed like some made-up shit to Jeremy. Maybe they read the first two letters backwards? And then added more random letters? Whatever.
A-barb-igail turned out to be an older lady that also used to live in the neighborhood, but Mr. Garcia didn't know whether she still did, or whether she was even still *alive*. How reassuring. He also made sure to point out to Jeremy that he'd never liked people like her, too eccentric, too loud, and too fully of energy. Thanks, Mr. Grumpy.
He made his way down the street to where Abigail-slash-Barb's house was meant to be. With Jeremy's luck, he'd probably find some ruins there, or a small forest, or a full-on black hole. What he really found was a small bungalow with washed-out, light blue side panels. It looked a lot smaller than his new house, but the roof looked a little less steep and the foundation seemed different, so he didn't know for sure.
He rang the doorbell, *praying* for the person opening to be Barb, with a sign hanging around her neck that says "Hello, I am Barb, and you don't have to speak to more people. This whole adventure was not a mistake, and it is now over, you have successfully found me." A short woman with a long ponytail opened the door. Jeremy introduced himself, and tried to get to the point as quickly as possible.
This was not Barb. Of course, it wasn't. It was Lucy. Jeremy told the story of the box, and the butt-shaped pillow, and the adorable note, and the half-heart-shaped necklace, and Lucy listened with wide eyes, and she was beaming.
"My gran... she never *knew*," she suddenly said. Wait, her *grandma*?
"Wait, y-your *grandma*?", he stammered.
She laughed. "Abigail. Yeah. I would always call her 'Granny Abby', but she tried to get everyone to call her Barb. I don't know why."
Jeremy sighed. She couldn't have said this at the start? When he asked about a Barb? Well, at least *Lucy* had a normal name.
"Down the road, you said? The green house?", she asked.
His new house wasn't really green anymore, since nobody had repainted it in a few years, probably even a decade. Jeremy didn't mind, because he never saw the outside much himself, and he couldn't care less what some strangers down the road thought of the state and dilapidation of his house.
"Yea, Maple 17," he replied.
"Evelyn Sherwood", she muttered somberly. "Her and Abby were best friends. Ever since I can remember, they spend most of their time together. Of course, no one thought about it at the time because an old lady like that--people thought she'd have herself figured out, I guess. Maybe they just didn't talk about it much back then, either."
Lucy had been staring into the hallway from where she was sitting, with a kind of intensity that implied she was focusing on something. But there was nothing there. What was she picturing?
"Yea, people had to be a lot quieter about it back then." He paused for a second to arrange his thoughts. All of a sudden, he had a lot of those. Emotions, too.
"The letter makes so much more sense to me now," he finally said. "She said that she was too scared to tell your grandma. Maybe it wasn't just about the anxiety for her."
"Yea," she said under her breath, which had become heavier. "I can't believe she never-"
Lucy let her head drop gently onto her folded arms, which were resting on the table. Jeremy sat there quietly, not knowing what to do. Surely, anything would be better than just sitting there like a fucking idiot, he thought. So he took the leap and trusted his instinct.
"I'm so sorry, Lucy," he said. He waited a second in an attempt to figure out if she heard him or not. "When did she pass?", he asked finally.
It took a few moments for Lucy to catch her breath. Awkwardly, she used the sleeves of her purple sweater to wipe away her tears, along with most of her makeup.
"It's been almost a year now," she muttered. "We're still not fine. I know it's- I know it seems pathetic, but my mom hasn't recovered yet, either. She never spent more than a week away."
Jeremy hastily searched his jacket and jeans pockets for a pack of tissues. Once he finally found it, he placed it on the table in front of her.
"It's not pathetic at all," he managed. He was awful at social situations, let alone situations in which someone was crying because they suffered a tragic loss. Jeremy was completely out of his depth here, but somehow, he still felt like he was managing fine.
"I know," she said sniffly. "It's just hard for us." She took one of the tissues. "Thanks."
Jeremy looked at Lucy as she blew her nose and used a second tissue to pat her face dry. It was a mess now, with her black eyeshadow having spread lines all over her face. But he didn't mind at all. He understood emotions well (at least he liked to think so), and he understood how hard it was to lose a loved one.
They sat for a little longer before Lucy recovered somewhat and went to make some tea for the two of them.
***
A few days had passed, and Jeremy found out a lot more about Barb, Evelyn and their story. He and Lucy had been talking frequently, and with every time she talked about their story, she seemed to lighten up more. It started out just like that first day, with her crying, and Jeremy feeling awful about asking in the first place. But as their conversations went on, Lucy started to seem excited about the love story that could have been.
That day, she called him on the phone while he was in the kitchen, sitting over his laptop and eating a bowl of cereal. He picked up the phone.
"Oh my *God*," Lucy immediately began on the other end, with a kind of excitement in her voice that Jeremy didn't think he'd heard from her yet. "I completely forgot that they went to the same *retirement home*!"
Retirement home? "Retirement home?", he asked out loud.
"Abby and Ev! Like five years ago, my mom got busier with her job because of some big promotion, and obviously I was at school all day, so my grandma decided to move into a home, and she visited home every weekend. But she talked about that place all the time, because Evelyn lived there and talked about it all the time! *That's* why she said in the letter that they might never meet again!"
Jeremy finally understood what she was implying, and he was smiling now. "Wait, and so you think-"
She didn't interrupt him, per se, as that was exactly what he'd wanted her to do. "*They shared a room*, Jer," she said, as if this was some big life-changing revelation for her. Maybe it was.
"Shared a *room*? I didn't even know you could do that in a retirement home," he said, half-surprised, half-excited.
"There is *no way* that Ev didn't admit it to her all that time. Absolutely no way. Jer, they were *literally roommates*, and none of us got it. Oh my God." Lucy seemed like she couldn't stop talking. It was all just flowing out of her, but Jeremy really enjoyed hearing her like this.
They talked for a little longer, and Lucy told more stories about Ev and Abby, and Jeremy loved listening to it all. He imagined her grandma and Evelyn, going on dates, and holding hands at the lake, and eating dinner together. Lucy imagined them sitting together, outside, on a porch swing, with their hands intertwined, looking out at the sunset. It was a perfect date straight out of a movie, the way she seemed to picture it.
After they both hung up, he finally finished his cereal, and he started thinking about the box again. Once he was done eating, he fetched it from on top of his kitchen cabinet, where he'd put it to avoid having to enter the attic again.
He grabbed the small, heart-shaped pillow out of the box and held it in his hand, squeezing it slightly. It was still kind of sticky, but this time around, Jeremy didn't seem to mind. Right now, he was feeling inexplicably brave.
He picked up his phone and started typing up a message to Lucy.
"Speaking of perfect dates," he started. "Wanna see a movie tonight?"

View file

@ -0,0 +1,189 @@
---
layout: blog
title: 🚢 The Cruise That Changed it All
description: "Tom didn't like his mom's friend's son Jaz, though he wasn't entirely sure why. But stuck on a ship with him, he found out a lot about him, and even more about himself."
tags: [Short Stories, Featured]
book: true
reedsy: https://blog.reedsy.com/short-story/pyifvi/
discuss: https://twitter.com/Ellpeck/status/1550207100785147906
---
Tom was not normally one to dwell on specifics, but when his mother reminded him that he'd promised he would join her on this cruise, he got just the tiniest bit defensive. Because while, yes, he'd *technically* promised her he'd join her, it wasn't necessarily without any coercion or guilt-tripping, both of which his mom was absolutely famous for. Because what his mom hadn't told him was that this cruise would be *two weeks long* and that her best friend Suzy and her incredibly annoying son Jaz would be joining them.
"Honey, you have to give Jaz a chance," Tom's mom was saying in what he perceived to be an overly accusatory tone. "I don't even understand why you don't like him."
She knew *exactly* why Tom didn't like him. "You know *exactly* why I don't like Jaz", he replied, in a similarly accusatory tone. Tom threw a half folded t-shirt at her, and she picked it up and sighed.
Jaz had always been a little too much for him, admittedly. Because, while Tom was somewhat outgoing and *did* occasionally like to go on parties, or hang out with friends, or get a little drunk, Jaz did all of those things *constantly* and never stopped talking about them, ever. We get it, dude, you're so good with the guys that there isn't a single one you haven't seen naked yet.
There was, Tom had to admit to himself, a little jealously hidden in that criticism of Jaz. Because occasionally Tom asked himself why he hasn't had sex yet---as any 20-something guy would, or so he thought. The only difference being that, according to what he heard from his friends---and Jaz---, usually people actually *wanted* to have sex, and enjoyed the idea of doing so.
Not Tom. Admittedly, he never quite understood what all the fuss was about. He felt kind of ashamed about it, too, in a way. He didn't really enjoy talking to his friends about sex, either. They'd all go on and on about how amazing it was, and how much they'd been looking forward to their first time. Meanwhile, Tom would always feel like there was something wrong with him for not being interested in pursuing that.
Maybe that contributed to his hatred of Jaz.
Or maybe Jaz was just really annoying.
***
Tom had his own cabin adjacent to his mom's one. A few days had passed on the ship and, despite his expectations, Tom was actually enjoying himself. Jaz's cabin was just down the hall from his, and occasionally they'd cross paths, but they wouldn't speak much. Tom didn't really want to, and it seemed like Jaz probably knew that he didn't want to.
One evening, after the four of them had eaten dinner together and their moms had stayed to grab drinks at the bar, Tom was stopped by Jaz in the hallway unexpectedly.
"Hey, dude, I wanna talk to you," he said after tapping him from the shoulder from behind. He sounded out of breath. Was he *running* after Tom? Kinda creepy.
Tom immediately felt himself pulling a face. "What? Why?", he demanded.
"Well," Jaz began while scratching the back of his head. He was just standing there now, looking... confused? Guilty? Worried? Tom couldn't tell.
"I feel like you don't like me, Tom," he said, before looking up again and meeting eyes with him. "And I want to know why."
Tom froze. Oh God, this was it. This was the type of situation that he *really* didn't know how to deal with.
"What?", he said after a couple seconds of deliberating whether to tell the truth. Apparently, his brain had accidentally decided not to tell the truth. Great. "I like you! Why wouldn't I?", he said.
Tom had averted his gaze, but now turned to meet eyes with Jaz again. Jaz looked *sad*. "You sure?", he asked. His eyes narrowed before he continued, "It's not because I'm trans, is it? Because my mom always gave me the impression that you'd be totally cool with that."
Jaz was...? He-- what? Tom just stood there and said nothing for a good ten seconds or so. Jaz closed his eyes and sighed.
"Wait, what?", Tom finally asked.
"Wait," Jaz began again. "You didn't *know*?"
Tom was embarrassed now. He felt his face heat up and Jaz could probably observe it turning bright red like a goddamn tomato.
"Oh my God, you're the first person in the *world* that didn't immediately realize. Are you *serious*?"
"I-- I'm sorry," Tom heard himself say.
"Dude! Sorry for *what*? I'm passing, I'm fucking *passing* and I didn't even *realize*. This is *amazing*!", Jaz exclaimed, and immediately pulled Tom in for a hug.
Tom felt some of his worries ease away, sure that Jaz had forgotten about the whole *Tom hates me and doesn't want to tell me why* situation. Tom reciprocated the hug, and they just stood there in the hallway for a minute, hugging like a couple of idiots.
But Tom felt vulnerable now. And, to him, vulnerable meant honest.
"Actually," Tom began after Jaz finally pulled away. "I used to think you were kind of... annoying?" Tom ended with a rising tone, as if to inquire whether Jaz understood what he meant. Instead, Jaz frowned. Oh, no.
"I-- don't get me wrong, I think you're really cool, and-- this is not about you being trans *at all*, but I just-- you always talk so much about the--", he stammered. He was making a fucking fool out of himself.
"Oh God," Jaz interjected. He turned bright red and averted his eyes. "You think I'm just some horny fuckboy, don't you?"
Weirdly, Tom was happy that Jaz just said clearly what Tom had tried so hard to eloquently describe. But how to continue this hell of a conversation now was the tricky part. So he didn't, and he just stared instead.
Jaz, out of nowhere, started to laugh. "I mean, you know... I kind of *am*. I don't blame you."
For some twisted reason, Jaz seemed to be totally fine with Tom essentially insulting his entire *lifestyle* just then, and so Tom decided that he absolutely had to fix this situation. Just *how*, he wondered.
"N-- no, it's not really that. I mean, kind of? It has to do with that. I-- whenever you talk about this-- whenever my *friends* talk about this--", he began, and immediately wished he hadn't. But there was no turning back now.
"It's not about the *amount* of sex you have. Like, at all. It's just that... you all seem so *excited* about having sex, and I'm happy that you are, but I've never felt that way about it. Talking about it just makes me feel kind of... weird? Like, why am I not interested in this? Why is everyone excited by the idea, and I'm not? Is there something wrong with me? I don't--" Tom started to cry now. Tears were flowing from his stupid eyes like a stupid goddamn waterfall, and he was just standing there like a stupid fucking idiot.
And then Jaz laid his hand on Tom's shoulder and frowned again. "Dude," he said. "There's nothing wrong with you. Not everyone's interested in sex."
Tom tried to wipe his eyes on his sweatshirt sleeve without much success. "Clearly, you haven't talked to a human before."
Jaz chuckled. "I'll have you know that I've talked to plenty of asexual people in my life."
Asexual people?
"Asexual people?", Tom asked, puzzled.
"Ah," Jaz said and smiled. "Well. When someone identifies as asexual, they usually aren't interested in having sex, or they're only interested in having sex in certain situations, like with a partner they feel emotionally connected to. Well, it's a spectrum, really. But it's not *weird* or *broken*. At all."
Wow. Well... that was a whole-ass *revelation* to Tom. He stared at Jaz, who was looking back and still smiling. "Oh my God," Tom finally said. "I'm such an idiot."
Jaz laughed, again, because apparently that was all he did nowadays. "Dude, it's okay," he said. "Not everyone knows everything."
That was true, Tom had to admit. But how had he not known about this? Jaz made it sound like there was this entire *community* of people that probably felt exactly like he did, and he never cared to do *any* research about this? He felt like a proper idiot now.
"I guess," he began again. "I never did any research on this because... I just felt so strange about it," he admitted to himself and to Jaz.
Jaz nodded, and pulled him in for a hug again.
Before this, Tom had never known how thoughtful Jaz was. How caring.
***
Tom did not want to admit it, but that interaction had really changed things for him. He was tossing and turning in his bed, going back to grabbing his phone and looking up asexuality on the internet over and over again. He blamed himself. He couldn't believe that it had taken him this long to find out about it. He knew that people always say that "it's never too late to find yourself", and that you shouldn't blame yourself for struggling with your identity. But he blamed himself anyway, because that's just what he did.
When it got to the morning hours and the light started creeping into his cabin, Tom pulled himself out of bed and put on the first coherent outfit he could find. Like someone from those teen movies he occasionally watched with his mom, he mulled it over in the mirror before switching to a different one. While undressing, he wondered why he was even doing this---something he didn't recall ever doing before.
It wasn't for Jaz. That much was clear.
It was for a totally different occasion that had nothing to do with Jaz.
Yea.
***
The two families met up for breakfast, just like they'd done every day while on the cruise, and Jaz sat down opposite Tom and smiled. After engaging in various conversations about the previous few days, the night and the plans for the day ahead of them, their moms left to go to the bathroom. Tom was about to get up to get more orange juice, but he hesitated when he noticed that Jaz seemed to be intently staring at him.
Jaz's light blue eyes were fixed to his, and after a few seconds, Jaz started to smile. Sitting there across from Jaz, Tom noticed for the first time how beautiful his eyes looked, and how his dark brown bangs were covering his eyebrows. He wasn't sure if Jaz styled them or if they were naturally this curly, but he felt it'd be weird to inquire about that now. Eventually, Jaz picked up his own glass and started walking away, presumably to get more juice for himself. Too mesmerized by Jaz's eyes, and movements, and his stupid curly bangs bouncing as he moved, he totally forgot to ask Jaz to get him a refill too, or to just tag along to the juice bar. What was going on with him?
After Jaz was out of eyeshot, Tom decided to get his act together and started focusing on his breakfast again, slowly working away at the massive pile of tasty things he'd picked up from the buffet. When Jaz came back a minute or so later, he plonked down a full glass of orange juice next to Tom's empty one, and then placed down another one next to his own plate. Tom was confused for a moment, because social cues weren't necessarily his strong suit, but after a moment of silence, Jaz smiled down at him and said, "Thought you might want some more juice."
"Thanks," Tom said quietly and lowered his head again, trying to focus on the massive tower of vegetables, bread rolls and cold cuts in front of him.
Jaz was really thoughtful, he thought to himself then, and felt his face getting warmer. He was blushing. How embarrassing. This was just a meaningless gesture, right? Nothing to blush over. Nothing to obsess about. Right?
After their moms came back, Tom sat in relative silence for the rest of the meal.
***
After breakfast, Tom's mom suggested going to the pool on the roof. Jaz's mom seemed skeptical at first, but was easily swayed by her son's suggestion of a big glass of lemonade and a deck chair.
Tom did not like the pool idea. He'd always been insecure about his looks, especially his belly and his legs, which was why he rarely ever wore shorts in the summer. Meanwhile, Jaz always rocked crop tops and cute hats, and yea, maybe Tom was a little jealous of that. Sometimes, he wished he could have the effortless confidence that Jaz seemed to have.
When they got to the pool, their moms immediately pulled off their bright summer tops and jumped into the water, but Tom hesitated, because of course he did. He stood for a minute or two and just observed the water, the little waves and ripples it created and the effects that the bright midday sun had on it. He noticed that Jaz hadn't pulled his shirt off yet either, and that he was standing almost as awkwardly as Tom was. He'd never seen Jaz like this, he thought. Tom observed Jaz pulling his shirt up slightly, but then stopping just shy of his chest and letting go of the fabric again. It took him a few seconds, but eventually he understood why.
He understood anxiety like this. This was his *thing*, and for once, he felt like he knew how to talk about it.
"Hey, are you okay?", he asked quietly as he put a hand on Jaz's shoulder.
"Yea, I'm good. Just don't wanna take off this shirt, you know?", Jaz replied sheepishly and lowered his head again.
Tom asked why, even though he was almost certain that he knew why.
"Well, the...", Jaz started, but then hesitated. He turned a bit so Tom and Jaz were facing each other straight on, and then pulled up his shirt slightly higher than he had previously, revealing two horizontal scars on his lower chest. His top surgery scars, Tom thought to himself.
"These," Jaz said and pointed at one of them with his other hand. "They're from my top surgery a few years ago," he added.
"Oh," Tom said, trying to appear somewhat surprised. "They look cute, though."
For a second, Tom wasn't sure if he'd just said that out loud or just thought it in his head. But when Jaz started blushing and then smirking, he knew that he'd definitely said it out loud.
Oh, no.
"Well, I mean-- not *cute*, but-- well, not *not* cute-- they're like, when-- they--" Tom stopped for a second and took a deep breath. Jaz was still smirking, a little wider now.
"They're like--", Tom tried again. "They show that you went through something and came out on top, you know?" On *top*? Come on, Tom.
"Well, not-- on *top*, but like--", he began again. But Jaz put a hand on Tom's shoulder and laughed. "Don't worry, I get it."
Probably thinking about Tom's little wisdom, Jaz ran his hand along one of the scars. Finally, he said, "You're kind of right, you know? I shouldn't be embarrassed about them."
Tom breathed a sigh of relief before quietly telling him that, no, he doesn't have to be.
"Thanks, though," Jaz said and laughed again. "Wanna jump in together?"
***
At the end of the day, Jaz tapped Tom's shoulder in the hallway again, just like he did that one night, the night Tom noticed that there was something between them. Something other than hate or ambivalence.
Tom turned around and saw Jaz, his curly hair still messy ever since the pool, beaming the same way he did after Tom had talked to him about the scars. "Today was fun," Jaz said.
"Yea," Tom replied. "You're fun to spend time with," he added without thinking about it.
For some reason, he didn't feel like he had to take it back this time, though. Not only did he feel that it was true, he felt that Jaz had to *know* how Tom felt about him.
"Yea," Jaz said quietly, before taking a step closer to Tom. He seemed kind of insecure now, the way he was at the pool. Was there something else about his surgery? About his scars?
No, this seemed like a different kind of insecurity.
"So, uh," Jaz began, before moving in even closer. "We don't have to do anything else, but..." He gently took Tom's hand. "Do you maybe want to, uh, kiss?", he asked.
Jaz asked to kiss him, right then, in the hallway, where they had their first ever honest conversation. Where Tom learned that Jaz wasn't just some dumb idiot, but that he was funny, and caring, and *complex*.
Tom didn't reply; he just drew Jaz in for the kiss instead. And then they stood there, their hands intertwined, with their eyes closed and their lips touching for the first time.
Maybe it'd been meant to be like this all along.

View file

@ -0,0 +1,117 @@
---
layout: blog
title: 📚 My Favorite Reads of 2022
description: I read so many books this year! Here are some of my favorites, what I liked about them, and why you might like them too.
tags: [Reading, Featured]
mature: true
discuss: https://twitter.com/Ellpeck/status/1602678787220684802
---
2022 has been an interesting year, to say the least. I moved to a new city at the other side of the country, I continued my studies at a new university, and I read a *ton* of books.
Well, okay, not necessarily a ton by some people's standards. But seeing as I've only read about twelve books [last year](https://ellpeck.de/blog/reading_faves_2021/), and zero books the year before that, I think this year's twenty-nine[^1] books is a pretty good amount.
I'm tracking my reads using [the StoryGraph](https://app.thestorygraph.com/profile/ellpeck), which is an incredibly helpful tool for helping myself remember what I even read and how much I liked it. My favorite genre is romance, as you can quite clearly see looking at my [statistics](https://app.thestorygraph.com/stats/ellpeck?year=), but I try to read a nonfiction book from time to time, too.
Here's a list of some of the books I read this year that I enjoyed most. I'll try not to include any major spoilers in them, but I will talk about the story a bit to highlight the parts that I particularly enjoyed, and I'll also include some quotes I found funny, cute or otherwise relevant.
These aren't the *only* books I read this year that I'd recommend, by any means, but they're the ones I'd perhaps recommend most if you happen to like similar stuff to what I like. These are in no particular order, so this isn't a ranked list by any means.
# 📱 They Both Die at the End (and The First to Die at the End)
So, right off the bat, I need you to know that I don't usually read books with sad endings. While I agree that crying can be freeing, I also have a pesky habit of getting depressed about fictional dead characters for a week after they die. Which I don't particularly enjoy.
But when I saw *They Both Die at the End* by Adam Silvera, I didn't *know* it was going to be as bittersweet as it was. Well, I had some idea based on the title, of course, but for some reason I didn't clock that it was going to be a romance. A romance where both characters die at the end. *I cried so much.*
*They Both Die at the End*, and its prequel, *The First to Die at the End*, are set in a fictionalized universe where a service exists that will tell you about the day you're going to die. On the day, they call you on the phone and deliver the frankly disturbingly true prediction. They don't tell you why, or how, you're going to die, but they tell you that you *will* definitely die on that day. The stories are told from multiple people's perspectives, with each chapter being told either by one of the two main characters, or occasionally by some of the many side characters. The people dying, called "Deckers" (for a somewhat silly reason that will become apparent in the prequel), are encouraged to make their last day beautiful, and fill it with as many memorable moments as possible. In both books, this includes a boy falling in love with another boy (who may or may not also die today). They have a beautiful one-day romance, until death happens at the end and the reader cries themselves
to sleep, replaying the scene in their head for hours.
If you like close-to-reality sci-fi, and heartbreakingly tragic queer romance stories, then I really recommend these two books.
> "What the hell am I supposed to say?" I ask. "Speak from your heart," Dalma says. "Not your dick." "Speak from my heart, not my dick; speak from my heart, not my dick," I mutter like a mantra.
> He's got to be thinking I'm prepping for a kiss, which I mean, I'm not, but also, I wouldn't be mad at all. Damn, maybe I am thinking with my dick.
> "You're positive about a movie theater in the afterlife but not if you'll have your glasses? Seems like an oversight in your heavenly blueprint." He removes my glasses and puts them on. "Wow. Your eyes suck."
# ⚱️ I'm Glad My Mom Died
*If you struggle with physically or emotionally abusive relatives or severe eating disorders, this book and this section of the blog post may not be for you.*
We all know Jennette McCurdy, right? If you don't, she's an ex-actress (now writer and director) who used to work for Nickelodeon, with her most popular role having been Sam Puckett in *iCarly* and later *Sam & Cat*.
I'm Glad My Mom Died is her memoir about her childhood and early adulthood, which was hugely influenced by her abusive mother who forced her daughter into eating disorders and acting, neither of which McCurdy was particularly interested in herself. The book is deeply tragic, but also hopeful, and it depicts the actress, her family, her characters, and the people she worked for, in a raw and heartbreaking way.
I don't want to say that much more about the book's content here because I don't want to glamorize or romanticize McCurdy's experiences, but I do want to invite you to read the book for yourself. It really gives you another insight into how much of people's trauma isn't visible to the outside world, and how seemingly impossible it is to get out of an abusive situation when that situation your entire life. It also gives you a deep appreciation for honesty, openness, and mental health support through intimate friendships and therapy. It's also incredibly well written, making you feel like you're reading a diary that isn't meant to be read by anyone but the one who wrote it. The book's second section is also deeply hopeful, showing McCurdy's escape and subsequent healing from her traumatizing past.
# 🚀 Love on the Brain
Ali Hazelwood is a romance author who seems to primarily write stories related to academia in some form. Being a student, this should be deeply relatable to me, but being an antisocial mess, I rarely ever actually interact with people at university. Nevertheless, I read two of Ali's books this year: The Love Hypothesis in the first half, and Love on the Brain, her most recent release.
While I think they are both amazing, I'm passionate about Love on the Brain because it contains two adorable romances, a cast of hilarious characters, and a cute cat that may or may not be real. In the book, the main character Bee starts a job at NASA along with her assistant, where she meets her apathetic, but secretly adorable, love interest Levi.
If you love a good wacky romance, a punky queer feminist, and a chaotic story, you'll love... on the Brain.
> "Sorry." I scratch the back of my neck. "Just to be clear, you two were having intercourse with..." "With each other," she tells me proudly. "How did Boris even ... find you?" "Guy cam into our office looking for something, found us on your desk, ratted us out." "On *my*---why did you have to do it on *my*---" I stop. Take a deep breath. "To be clear." I look between them. "This was ... consensual?" "Very," they answer in unison, locking eyes and smiling like idiots.
> As I wait for the crowd to disperse, I take stock of the room. His team appears to be WurstFest™ material. The well-known Meatwave. A Dicksplosion in the Testosteroven. The good old Brodeo.
# ✊🏿 We Are Not Like Them
The story of We Are Not Like Them, by Christine Pride and Jo Piazza, is... *intense*, to say the least. The book is told from the perspectives of a pair of childhood friends: Riley, a black news anchor, and Jen, a white woman with a policeman husband named Kevin.
I think you can see where this is going.
After Kevin shoots an unarmed black teenager, Jen has to reevaluate her relationship with her husband while Riley covers the events and aftermath on the news. The book perfectly delivers the heartfelt, troubling accounts of both women, their interactions with each other, and the difficult question of what it all means for their relationship. In emotionally packed and heartbreaking descriptions, we also see Riley reporting on, and subsequently getting closer to, the mother of the victim and the rest of their family.
If you're into troubling, but deeply honest depictions of racial issues, and the effect they have not only on the victims and their families, but also on everyone around them, I highly recommend We Are Not Like Them.
> "They think it's so cool that my best friend is black." She rolled her eyes as she said it, but it was still clear that it was some sort of weird badge of honor for her, like I was a trendy accessory---otherwise why mention it at all?
> "You know, I used to think you were such a weirdo for getting annoyed when people want to touch your hair, but now that I've got this"---she places a hand on either side of her stomach---"I get it now. I'm like Aladdin's lamp. No one asks. They just rub." It isn't the same thing at all, but I let it go.
# 🏳️‍⚧️ Meet Cute Diary
Okay. Okay. Let's get back to *adorable stories*. The Meet Cute Diary, by Emery Lee, is a story about Noah, a trans boy who runs a blog that features stories about trans people finding love through adorable meet cutes. The only problem: They're all fake, and no one knows Noah is making them up.
It's a beautiful and sometimes heartbreaking story about what trans people go through, and the struggles that extend far beyond strangers and straight into personal relationships. It's also incredibly hopeful, though, depicting Noah as someone who gets more comfortable with himself and his identity while figuring out who is the one for him.
The story also includes a character who is trying to figure out which pronouns suit them best, and the book cleverly incorporates this process into the story by using the pronouns they're currently trying while talking about them.[^2]
> "They're your pronouns. You don't have to consider anyone else before you pick them." His eyes widen, and then I don't know hat changes, but he smlies like all his problems have melted away, and it really is a beautiful smile. "Thanks. Do you mind using they/them for me from now on?" I roll my eyes. "No, I don't mind. They're your pronouns." And they smile again, and for a moment, my heart feels heavy.
> He smirks over at me and says, "This is Noah, my boyfriend. Noah, this is Matt. We used to ski together." "Damn, dude, I didn't know you were gay," Matt says. Drew laughs. "I'm not. Noah's special." And I know he means it as a compliment, but it makes me feel kind of dirty, like I'm just not enough of a boy or too much of an anomaly to really be anything at all. I mean, it's not like he's a trans rights activist. He doesn't know better, so I'll just mention it when I get a chance. No big deal.
(*It is a big deal, though. This is awful. Don't be like this, please.*)
# 🌧️ The Sky Blues
The Sky Blues, by Robbie Couch, is a beautiful coming-of-age story about a high school student named Sky, who ends up starting a revolution at his school. After deciding to ask out a guy to Prom, his secret plans get leaked and an e-blast is spread around the school, containing and subsequently causing a wildfire of homophobic and racist rhetoric. But Sky doesn't back down, and his friends and allies quickly assemble a protest that threatens to get the prom canceled, but that might also save Sky's leaked promposal plans.
I absolutely adore this book. However, I do have to admit that some parts felt hard to read because they reminded me of my high school years, where I was still closeted due to the very fears that come true in The Sky Blues. It also gave me hope though, that younger people can feel empowered by the story and rise up for themselves or their queer friends as well.
It also contains the best teacher that has ever existed (or, well, *fictionally* existed), and I want her to have been my teacher in high school.
> "Please, for the love of God, don't tell me you were masturbating." "Stop!" "You totally were, weren't you?" "I can barely tie my shoes before eight a.m. I don't have the motivation to do that before school. I was shower-dreaming about him, sure. But that's it." "Shower-dreaming?" She tils her head, confused. "Is that gay for 'masturbating'?"
> "Principal Burger." Winter smiles. "Surely we can figure this out without taking prom away from students?" "Ms. Winter." Burger returns the smile, but maliciously. "How about this? Unless you plan on chaperoning your own prom for your Yearbook class, you'll need to enforce the ban." "Well," Winter sighs, thinking. "Okay, then." Burger blinks. "Okay you'll enforce the ban?" "Okay I'll be chaperoning my own prom for my Yearbook students," she says.
# 🔥 Smoke Gets In Your Eyes
Lastly, but certainly not least...ly, a great nonfiction book by one of my favorite YouTubers and activists: Caitlin Doughty, a mortician from Los Angeles who specializes in advocating for people having access to the body disposition options they want after their death.
Smoke Gets In Your Eyes is her first book (and my favorite of the three she's published so far), and it's about her early twenties, where she worked as a crematory operator. It's packed with funny, heartfelt, and sometimes rather serious stories, but also contains a ton of fascinating information about the funeral industry. As almost all of her content, it's also delightfully macabre in an incredibly endearing way.
If learning about deathcare traditions in various cultures is more your stlye, I also thoroughly recommend her From Here to Eternity, which, as the subtitle suggests, is all about traveling the world to find the good death.
> To my great annoyance I found him stacked below Mr. Willard, Mrs. Nagasaki, and Mr. Shelton. That meant stacking and restacking the cardboard boxes like a game of body-fridge Tetris.
> “Yes, Mother?” I probably called her Mom or Mommy, but in my memories Im a very polite British child with exquisite manners.
> But young lovers take note: above all else, the phrase every girl truly wants to hear is “Hi, this is Amy from Science Support; Im dropping off some heads.”
> The anxiety I felt was no longer caused by the fear of an afterlife, of pain, of a void of nothingness, or even a fear of my own decomposing corpse. All my plans and projects would come to an end. The last thing preventing me from accepting death was, ironically, my desire to help people accept death.
# Closing Remarks
I hope you enjoyed this little look into some of the books I really enjoyed this year. As I said at the top, for a more in-depth look at my kind of books, you can check out my profile on [the StoryGraph](https://app.thestorygraph.com/profile/ellpeck).
I also write my own short stories occasionally, so if you're into cutesy romantic stuff, you can check them out [here on the blog](https://ellpeck.de/#blog-short-stories) as well.
Feel free to let me know what you think about my picks, and also feel free to let me know what you read this year, and if you have any recommendations for what I should read next year. ❤️
[^1]: And counting! *The year is not done yet.*
[^2]: They don't end up using they/them pronouns in the end, but I feel it would take away from the storytelling to tell you the pronouns they do end up using.

View file

@ -1,6 +1,28 @@
*TLDR: Actually Additions for 1.16.4 in the future. No ETA. No Fabric port. No 1.13, 1.14 or 1.15. Beautiful art overhaul. Don't ask Ellpeck anything ever. Stay awesome.*
---
layout: blog
title: 🔮 The Future of Actually Additions
description: Not wanting to accept the fate of Actually Additions, someone has come to its rescue. 1.16, here we come?
tags: [Minecraft]
discuss: https://twitter.com/Ellpeck/status/1330938597785169925
---
Before I start this post, I want to re-iterate that I haven't had a direct programming-related connection to Actually Additions in [over three years](https://github.com/Ellpeck/ActuallyAdditions/commits/main?after=896a082d747a3e19755ded1973544d59fa992787+244). I don't plan on changing this. This means that, if you have *anything* to say about the mod, be it issue reports or feature requests, **do not talk to me about that**. Go to [the issue tracker](https://github.com/Ellpeck/ActuallyAdditions/issues) or [my Discord](https://ellpeck.de/discord)'s `#minecraft` channel.
***TLDR: Actually Additions for 1.16.4 in the future. No ETA. No Fabric port. No 1.13, 1.14 or 1.15. Beautiful art overhaul. Don't ask Ellpeck anything ever. Stay awesome.***
# Updates to This Post
## March 2024
Development is [happening again](https://github.com/Ellpeck/ActuallyAdditions/commits/1.20.4/)! The, frankly, truly mad [Mrbysco](https://github.com/Mrbysco) has taken it upon himself to take porting matters into his own hands. [Flanks](https://github.com/Flanks255) is also helping. It's a whole thing. But yay!
## November 2023
Honestly, I don't know anymore. Progress has slowed to a halt, and with all the old-mod-renaissance stuff going on in 1.20, I'm semi-considering getting back into Actually Additions myself. I dunno. Let me know if you're interested in helping out or whatever.
## February 2023
Yes, the port is still being worked on. As pointed out numerous times, you can stay up to date with the port through [the `1.16` branch](https://github.com/Ellpeck/ActuallyAdditions/tree/1.16). Do not ask when the port will be finished. We don't know yet. It's a big task and the people working on it do so in their free time.
## November 2021
The port is still being actively worked on. You can stay up to date with the progress through [the `1.16` branch](https://github.com/Ellpeck/ActuallyAdditions/tree/1.16) on the GitHub repository.
# Why?
Before I start this post, I want to re-iterate that I haven't had a direct programming-related connection to Actually Additions in [over three years](https://github.com/Ellpeck/ActuallyAdditions/commits/main?after=896a082d747a3e19755ded1973544d59fa992787+244). I don't plan on changing this. This means that, if you have *anything* to say about the mod, be it issue reports or feature requests, **do not talk to me about that**. Go to [the issue tracker](https://github.com/Ellpeck/ActuallyAdditions/issues) or [my Discord](https://link.ellpeck.de/discordweb)'s `#minecraft` channel.
Okay, now that that's out of the way... let's talk about the future of Actually Additions. In [my last post](https://ellpeck.de/blog-actually_additions), I talked about the things I disliked about the mod and, in that same vein, the reasons that I don't want it to be updated beyond Minecraft 1.12. What I didn't necessarily make clear in that post is that this isn't really my decision, because I haven't touched Actually Additions in *a long time*. For the last few years, **Shadows-of-Fire** has been maintaining the mod (that is, fixing issues and publishing updates), which is also the reason that there haven't been any new features during that time. So what about now?
@ -14,15 +36,15 @@ So, as a result of that, **MiKeY** is basically the new, official maintainer of
A little while later, another person approached me: [Ridanisaurus](https://github.com/Ridanisaurus), who apparently really likes making beautiful art for mods. He asked me if I want new art for Actually Additions, and since I have some *opinions* about the current state of Actually Additions' graphics and visual consistency, I naturally said yes immediately. So yea, **Ridanisaurus** is basically the new, official artist of Actually Additions.
# What?
MiKeY and Rid are going to port Actually Additions to **Forge** for **Minecraft 1.16.4**. They are re-doing all of the art for the mod and they will also modify some of the features that I dislike, based on my personal feedback and the information outlined in [my diss track](https://ellpeck.de/blog-actually_additions).
MiKeY and Rid are going to port Actually Additions to **Forge** for **Minecraft 1.16.4**. They are re-doing all of the art for the mod and they will also modify some of the features that I dislike, based on my personal feedback and the information outlined in [my diss track](https://ellpeck.de/blog-actually_additions).
The art overhaul has already started (and even been finished, I believe), and because it is *gorgeous*, I'm going to show you some of the preview pictures that Rid has sent me right now. Enjoy.
![](blog/res/future_actually_additions/1.png =100%x*)
![](faa_1.png)
![](blog/res/future_actually_additions/2.png =100%x*)
![](faa_2.png)
![](blog/res/future_actually_additions/3.png =100%x*)
![](faa_3.png)
Don't they look *so good*?
@ -33,12 +55,12 @@ Since I know a lot of people will ask questions about this, here is a list of so
- We will *not* be keeping all of the features in the mod the same, especially not the Storage Crate, which will be getting a major overhaul.
- There will be *no* entirely new features, only changes to existing ones. Since this has been the case for the last few years as well, I don't think it's very surprising.
MiKeY has expressed great interest in helping me overhaul the features that I dislike and, as a result, making Actually Additions a better and more refined mod than it used to be.
MiKeY has expressed great interest in helping me overhaul the features that I dislike and, as a result, making Actually Additions a better and more refined mod than it used to be.
# When?
# When?
We don't know yet. *Please* don't ask.
# Thank You
So yea, that's about it for the news. I hope all of you are as excited about the port and the upcoming changes as I am. If you want to be kept up to date with the changes, you can check the [GitHub repository](https://github.com/Ellpeck/ActuallyAdditions) every once in a while (especially [its `clean-start` branch](https://github.com/Ellpeck/ActuallyAdditions/tree/clean-start)) and join [my Discord server](https://ellpeck.de/discord), where we might post some updates from time to time.
So yea, that's about it for the news. I hope all of you are as excited about the port and the upcoming changes as I am. If you want to be kept up to date with the changes, you can check the [GitHub repository](https://github.com/Ellpeck/ActuallyAdditions) every once in a while (specifically [its `1.16` branch](https://github.com/Ellpeck/ActuallyAdditions/tree/1.16)) and join [my Discord server](https://link.ellpeck.de/discordweb), where we might post some updates from time to time.
As an additional note, I would like to thank all of you for the continued support of not only Actually Additions, but all of my mods and even my non-Minecraft-related projects. Of course, there have also been some rude people, but all in all, yall have been an amazing community and I am so grateful that you enjoy the things I create. I love yall. ❤️
As an additional note, I would like to thank all of you for the continued support of not only Actually Additions, but all of my mods and even my non-Minecraft-related projects. Of course, there have also been some rude people, but all in all, yall have been an amazing community and I am so grateful that you enjoy the things I create. I love yall. ❤️

View file

Before

Width:  |  Height:  |  Size: 632 KiB

After

Width:  |  Height:  |  Size: 632 KiB

View file

Before

Width:  |  Height:  |  Size: 380 KiB

After

Width:  |  Height:  |  Size: 380 KiB

View file

Before

Width:  |  Height:  |  Size: 630 KiB

After

Width:  |  Height:  |  Size: 630 KiB

View file

@ -1,4 +1,12 @@
So it's been a hot minute since I stopped working on my first big game project, [Rock Bottom](https://rockbottomgame.com). Since then, I've changed a lot, but the game hasn't changed that much: For a long time, the project was vacant, until I decided to make it open source. From that point on, a couple of my friends started working on it, adding some new features and fixing some bugs, until it seemingly fell back into vacancy over the last couple of weeks.
---
layout: blog
title: ⬇️ How to make a Rock Bottom mod
description: My adventures back into a game I stopped working on about two years ago and how I start on a mod for it
tags: [Programming]
discuss: https://twitter.com/Ellpeck/status/1180092634410487808
---
So it's been a hot minute since I stopped working on my first big game project, [Rock Bottom](https://rockbottomgame.com). Since then, I've changed a lot, but the game hasn't changed that much: For a long time, the project was vacant, until I decided to make it open source. From that point on, a couple of my friends started working on it, adding some new features and fixing some bugs, until it seemingly fell back into vacancy over the last couple of weeks.
So let's port my recent Minecraft mod, [Nature's Aura](https://www.curseforge.com/minecraft/mc-mods/natures-aura), to Rock Bottom!
@ -13,7 +21,7 @@ Okay, finding [a build of the game](https://github.com/RockBottomGame/RockBottom
Hm. Okay.
So something's already broken.
![](blog/res/rock_bottom_mod/1.png =100%x*)
![](rbm_1.png)
It looks like here
```
@ -27,7 +35,7 @@ That didn't seem to fix it either. Huh. Taking a look at [the maven](https://mav
Okay, it's now the next day and it looks like the maven has been fixed, which is nice. So all the compile issues are finally resolved, I put the build into the `/gamedata` folder like explained in the tutorial, I renamed the examplemod to `NaturesAura`, and I can now finally try running the game!
![](blog/res/rock_bottom_mod/2.png =100%x*)
![](rbm_2.png)
Ah! That worked quite well in the end.
# Actually making something
@ -76,7 +84,7 @@ public void preInit(IGameInstance game, IApiHandler apiHandler, IEventHandler ev
So far, so good. Let's figure out how to add a texture to the thing.
I somewhat remember that I made a horrible json-based asset system (instead of just loading all of the assets in the mod's jar automatically), so I'm going to try to add my tile to the `assets.json` file the example mod provided me with and also add a texture into the actual file system.
![](blog/res/rock_bottom_mod/3.png =100%x*)
![](rbm_3.png)
This is the folder structure I decided on. I also created a quick golden version of the game's leaves texture by going to the [asset repository](https://github.com/RockBottomGame/Assets), stealing the leaves texture and recoloring it to be golden-ish.
Also, I put this in the assets file, but I have no idea if that's actually the right path. We'll find out.
@ -101,7 +109,7 @@ public static final Tile GOLDEN_LEAVES = new TileGoldenLeaves(NaturesAura.create
```
*Crisis averted*.
![](blog/res/rock_bottom_mod/4.png =100%x*)
![](rbm_4.png)
Yaaay, it... worked? *Somewhat?*
You're probably yelling at your screen by now, but yes, I finally noticed it as well: My assets path says `examplemod` instead of `naturesaura`. Easy fix, though.
@ -119,7 +127,7 @@ While I'm at it though, I can also add a localization entry for the golden leave
Let's try again.
![](blog/res/rock_bottom_mod/5.png =100%x*)
![](rbm_5.png)
Ta-da! Success, at last.
After some investigation, I realized that Rock Bottom's normal leaves can be walked through, so let's see how to make that happen. Typing `@Override` while in the `TileGoldenLeaves` class causes my IDE to list all of the methods I can override. Among them are two that interest me:
@ -136,13 +144,13 @@ public BoundBox getBoundBox(IWorld world, TileState state, int x, int y, TileLay
```
It seems like this is what I have to do to make the tile walk-through...able. Let's try it out.
![](blog/res/rock_bottom_mod/6.png =100%x*)
![](rbm_6.png)
Yay, that seems to have worked. Great.
# Making an item
Now that I have somewhat of a grip of this whole Rock Bottom stuff again, I'm quickly going to make an item. I won't bore you with the details as it's pretty similar to making a tile, but the gist of it is this: I made an `ItemGoldPowder` class that extends `ItemBasic`, and I initialized and registered an instance of that in my newly created `Items` class, of which I created an instance in my mod class's `preInit` method so that it gets initialized at the right time. Also, I did all of the annoying asset mumbo jumbo.
![](blog/res/rock_bottom_mod/7.png =100%x*)
![](rbm_7.png)
*Oh, C#, you've ruined me.*
# Making stuff happen
@ -164,7 +172,7 @@ While writing this code, I quickly remembered that Rock Bottom has a tile state
Let's try it out! ...yea, no. The Java version that gradle uses to compile a Rock Bottom mod isn't new enough yet: You can't use the `var` keyword. Oh, my poor, poor C# soul. So let's swap that `var` out for a `TileState`.
![](blog/res/rock_bottom_mod/8.gif =100%x*)
![](rbm_8.gif)
Yay, it works! *Except that, in the gif, the mouse position is weirdly offset for some reason. It's correct in person, I promise!*
# Conclusion
@ -172,6 +180,6 @@ So yea, that was the start of my adventure back into Java, back into my old game
I think it's important to remember that, as a developer (especially an indie developer), you don't have to code everything perfectly or work stuff out correctly the first try. I mean, heck, this is my own game, and I completely forgot how to make a mod for it. But it was fun to figure it out again, and to get back into something I made two years ago.
Oh, also, if you really want, [here's a build of the mod](https://ellpeck.de/blog/res/rock_bottom_mod/NaturesAuraRockBottom-0.1.jar) that you can try out yourself, as the game is now actually open source and available to everyone! I created this jar with the command `gradlew build`, and all you have to do to run it is [download the game](https://github.com/RockBottomGame/RockBottom/releases), run it once, and then stick the mod jar into its `mods` folder. It really doesn't do that much right now, though, so I don't know why you'd bother.
Oh, also, if you really want, [here's a build of the mod](NaturesAuraRockBottom-0.1.jar) that you can try out yourself, as the game is now actually open source and available to everyone! I created this jar with the command `gradlew build`, and all you have to do to run it is [download the game](https://github.com/RockBottomGame/RockBottom/releases), run it once, and then stick the mod jar into its `mods` folder. It really doesn't do that much right now, though, so I don't know why you'd bother.
As always, thanks for reading!
As always, thanks for reading!

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Some files were not shown because too many files have changed in this diff Show more