diff --git a/code/game/machinery/newscaster/newscaster_machine.dm b/code/game/machinery/newscaster/newscaster_machine.dm index 99ea0d31620..e667fe5b96f 100644 --- a/code/game/machinery/newscaster/newscaster_machine.dm +++ b/code/game/machinery/newscaster/newscaster_machine.dm @@ -176,14 +176,15 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/newscaster, 30) data["crime_description"] = crime_description var/list/wanted_info = list() if(GLOB.news_network.wanted_issue) - if(GLOB.news_network.wanted_issue.img) + var/has_wanted_issue = !isnull(GLOB.news_network.wanted_issue.img) + if(has_wanted_issue) user << browse_rsc(GLOB.news_network.wanted_issue.img, "wanted_photo.png") wanted_info = list(list( "active" = GLOB.news_network.wanted_issue.active, "criminal" = GLOB.news_network.wanted_issue.criminal, "crime" = GLOB.news_network.wanted_issue.body, "author" = GLOB.news_network.wanted_issue.scanned_user, - "image" = "wanted_photo.png" + "image" = (has_wanted_issue ? "wanted_photo.png" : null) )) //Code breaking down the channels that have been made on-station thus far. ha @@ -431,7 +432,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/newscaster, 30) return TRUE if("printNewspaper") - print_paper() + print_paper(usr) return TRUE if("createBounty") @@ -600,22 +601,14 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/newscaster, 30) * This takes all current feed stories and messages, and prints them onto a newspaper, after checking that the newscaster has been loaded with paper. * The newscaster then prints the paper to the floor. */ -/obj/machinery/newscaster/proc/print_paper() +/obj/machinery/newscaster/proc/print_paper(mob/user) if(paper_remaining <= 0) balloon_alert_to_viewers("out of paper!") return TRUE SSblackbox.record_feedback("amount", "newspapers_printed", 1) - var/obj/item/newspaper/new_newspaper = new /obj/item/newspaper - for(var/datum/feed_channel/iterated_feed_channel in GLOB.news_network.network_channels) - new_newspaper.news_content += iterated_feed_channel - if(GLOB.news_network.wanted_issue.active) - new_newspaper.wantedAuthor = GLOB.news_network.wanted_issue.scanned_user - new_newspaper.wantedCriminal = GLOB.news_network.wanted_issue.criminal - new_newspaper.wantedBody = GLOB.news_network.wanted_issue.body - if(GLOB.news_network.wanted_issue.img) - new_newspaper.wantedPhoto = GLOB.news_network.wanted_issue.img - new_newspaper.forceMove(drop_location()) - new_newspaper.creation_time = GLOB.news_network.last_action + var/obj/item/newspaper/new_newspaper = new(loc) + playsound(loc, SFX_PAGE_TURN, 50, TRUE) + try_put_in_hand(new_newspaper, user) paper_remaining-- /** @@ -677,6 +670,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/newscaster, 30) new_feed_comment.author = newscaster_username new_feed_comment.body = comment_text new_feed_comment.time_stamp = station_time_timestamp() + GLOB.news_network.last_action ++ current_message.comments += new_feed_comment usr.log_message("(as [newscaster_username]) commented on message [current_message.return_body(-1)] -- [current_message.body]", LOG_COMMENT) creating_comment = FALSE diff --git a/code/game/machinery/newscaster/newspaper.dm b/code/game/machinery/newscaster/newspaper.dm index d605d6257c3..e67910974a6 100644 --- a/code/game/machinery/newscaster/newspaper.dm +++ b/code/game/machinery/newscaster/newspaper.dm @@ -1,3 +1,8 @@ +/** + * Newspapers + * A static version of the newscaster, that won't update as new stories are added. + * Can be scribbed upon to add extra text for future readers. + */ /obj/item/newspaper name = "newspaper" desc = "An issue of The Griffon, the newspaper circulating aboard Nanotrasen Space Stations." @@ -10,167 +15,171 @@ attack_verb_continuous = list("baps") attack_verb_simple = list("bap") resistance_flags = FLAMMABLE - var/screen = 0 - var/pages = 0 - var/curr_page = 0 + + ///List of news feeed channels the newspaper can see. var/list/datum/feed_channel/news_content = list() - var/scribble="" - var/scribble_page = null - var/wantedAuthor - var/wantedCriminal - var/wantedBody - var/wantedPhoto + ///The time the newspaper was made in terms of newscaster's last action, used to tell the newspaper whether a story should be in it. var/creation_time + ///The page in the newspaper currently being read. 0 is the title screen while the last is the security screen. + var/current_page = 0 + ///The currently scribbled text written in scribble_page + var/scribble_text + ///The page with something scribbled on it, can only have one at a time. + var/scribble_page + + ///Stored information of the wanted criminal's name, if one existed at the time of creation. + var/saved_wanted_criminal + ///Stored information of the wanted criminal's description, if one existed at the time of creation. + var/saved_wanted_body + ///Stored icon of the wanted criminal, if one existed at the time of creation. + var/icon/saved_wanted_icon + +/obj/item/newspaper/Initialize(mapload) + . = ..() + register_context() + creation_time = GLOB.news_network.last_action + for(var/datum/feed_channel/iterated_feed_channel in GLOB.news_network.network_channels) + news_content += iterated_feed_channel + + if(!GLOB.news_network.wanted_issue.active) + return + saved_wanted_criminal = GLOB.news_network.wanted_issue.criminal + saved_wanted_body = GLOB.news_network.wanted_issue.body + if(GLOB.news_network.wanted_issue.img) + saved_wanted_icon = GLOB.news_network.wanted_issue.img + +/obj/item/newspaper/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) + if(held_item) + if(istype(held_item, /obj/item/pen)) + context[SCREENTIP_CONTEXT_LMB] = "Scribble" + return CONTEXTUAL_SCREENTIP_SET + if(held_item.get_temperature()) + context[SCREENTIP_CONTEXT_LMB] = "Burn" + return CONTEXTUAL_SCREENTIP_SET /obj/item/newspaper/suicide_act(mob/living/user) - user.visible_message(span_suicide("[user] is focusing intently on [src]! It looks like [user.p_theyre()] trying to commit sudoku... until [user.p_their()] eyes light up with realization!")) - user.say(";JOURNALISM IS MY CALLING! EVERYBODY APPRECIATES UNBIASED REPORTI-GLORF", forced="newspaper suicide") - var/mob/living/carbon/human/H = user - var/obj/W = new /obj/item/reagent_containers/cup/glass/bottle/whiskey(H.loc) - playsound(H.loc, 'sound/items/drink.ogg', rand(10,50), TRUE) - W.reagents.trans_to(H, W.reagents.total_volume, transferred_by = user) - user.visible_message(span_suicide("[user] downs the contents of [W.name] in one gulp! Shoulda stuck to sudoku!")) + user.visible_message(span_suicide(\ + "[user] is focusing intently on [src]! It looks like [user.p_theyre()] trying to commit sudoku... \ + until [user.p_their()] eyes light up with realization!"\ + )) + user.say(";JOURNALISM IS MY CALLING! EVERYBODY APPRECIATES UNBIASED REPORTI-GLORF", forced = "newspaper suicide") + var/obj/item/reagent_containers/cup/glass/bottle/whiskey/last_drink = new(user.loc) + playsound(user, 'sound/items/drink.ogg', vol = rand(10, 50), vary = TRUE) + last_drink.reagents.trans_to(user, last_drink.reagents.total_volume, transferred_by = user) + user.visible_message(span_suicide("[user] downs the contents of [last_drink.name] in one gulp! Shoulda stuck to sudoku!")) return TOXLOSS -/obj/item/newspaper/attack_self(mob/user) - if(!istype(user) || !user.can_read(src)) +/obj/item/newspaper/attackby(obj/item/attacking_item, mob/user, params) + if(burn_paper_product_attackby_check(attacking_item, user)) + SStgui.close_uis(src) return - var/dat - pages = 0 - switch(screen) - if(0) //Cover - dat+="
The Griffon
" - dat+="
Nanotrasen-standard newspaper, for use on Nanotrasen? Space Facilities

" - if(!length(news_content)) - if(wantedAuthor) - dat+="Contents:
" - else - dat+="Other than the title, the rest of the newspaper is unprinted..." - else - dat+="Contents:
" - if(scribble_page == curr_page) - dat+="
There is a small scribble near the end of this page... It reads: \"[scribble]\"" - dat+= "
Next Page
Done reading
" - if(1) // X channel pages inbetween. - for(var/datum/feed_channel/NP in news_content) - pages++ - var/datum/feed_channel/C = news_content[curr_page] - dat += "[C.channel_name] \[created by: [C.return_author(notContent(C.author_censor_time))]\]

" - if(notContent(C.D_class_censor_time)) - dat+="This channel was deemed dangerous to the general welfare of the station and therefore marked with a D-Notice. Its contents were not transferred to the newspaper at the time of printing." - else - if(!length(C.messages)) - dat+="No Feed stories stem from this channel..." - else - var/i = 0 - for(var/datum/feed_message/MESSAGE in C.messages) - if(MESSAGE.creation_time > creation_time) - if(i == 0) - dat+="No Feed stories stem from this channel..." - break - if(i == 0) - dat+="" - if(scribble_page == curr_page) - dat+="
There is a small scribble near the end of this page... It reads: \"[scribble]\"" - dat+= "

Previous Page
Next Page
" - if(2) //Last page - for(var/datum/feed_channel/NP in news_content) - pages++ - if(wantedAuthor != null) - dat+="
Wanted Issue:


" - dat+="Criminal name: [wantedCriminal]
" - dat+="Description: [wantedBody]
" - dat+="Photo:: " - if(wantedPhoto) - user << browse_rsc(wantedPhoto, "tmp_photow.png") - dat+="
" - else - dat+="None" - else - dat+="Apart from some uninteresting classified ads, there's nothing on this page..." - if(scribble_page == curr_page) - dat+="
There is a small scribble near the end of this page... It reads: \"[scribble]\"" - dat+= "
Previous Page
" - dat+="

[curr_page+1]
" - user << browse(dat, "window=newspaper_main;size=300x400") - onclose(user, "newspaper_main") -/obj/item/newspaper/proc/notContent(list/L) - if(!L.len) + if(!user.can_write(attacking_item)) + return ..() + if(scribble_page == current_page) + user.balloon_alert(user, "already scribbled!") + return + var/new_scribble_text = tgui_input_text(user, "What do you want to scribble?", "Write something") + if(isnull(new_scribble_text)) + return + add_fingerprint(user) + user.balloon_alert(user, "scribbling...") + if(!do_after(user, 2 SECONDS, src)) + return + user.balloon_alert(user, "scribbled!") + scribble_page = current_page + scribble_text = new_scribble_text + +///Checks the creation time of the newspaper and compares it to list to see if the list is meant to be censored at the time of printing. +/obj/item/newspaper/proc/censored_check(list/times_censored) + if(!times_censored.len) return FALSE - for(var/i=L.len;i>0;i--) - var/num = abs(L[i]) + for(var/i = times_censored.len; i > 0; i--) + var/num = abs(times_censored[i]) if(creation_time <= num) continue else - if(L[i] > 0) + if(times_censored[i] > 0) return TRUE else return FALSE return FALSE -/obj/item/newspaper/Topic(href, href_list) - var/mob/living/U = usr - ..() - if((src in U.contents) || (isturf(loc) && in_range(src, U))) - U.set_machine(src) - if(href_list["next_page"]) - if(curr_page == pages+1) - return //Don't need that at all, but anyway. - if(curr_page == pages) //We're at the middle, get to the end - screen = 2 - else - if(curr_page == 0) //We're at the start, get to the middle - screen=1 - curr_page++ - playsound(loc, SFX_PAGE_TURN, 50, TRUE) - else if(href_list["prev_page"]) - if(curr_page == 0) - return - if(curr_page == 1) - screen = 0 - else - if(curr_page == pages+1) //we're at the end, let's go back to the middle. - screen = 1 - curr_page-- - playsound(loc, SFX_PAGE_TURN, 50, TRUE) - if(ismob(loc)) - attack_self(loc) +/obj/item/newspaper/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Newspaper", name) + ui.open() -/obj/item/newspaper/attackby(obj/item/W, mob/living/user, params) - if(burn_paper_product_attackby_check(W, user)) +/obj/item/newspaper/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) return - if(istype(W, /obj/item/pen)) - if(!user.can_write(W)) - return - if(scribble_page == curr_page) - to_chat(user, span_warning("There's already a scribble in this page... You wouldn't want to make things too cluttered, would you?")) + switch(action) + if("next_page") + //We're at the very end, nowhere else to go. + if(current_page == news_content.len + 1) + return TRUE + current_page++ + if("prev_page") + //We haven't started yet, nowhere else to go. + if(!current_page) + return TRUE + current_page-- else - var/s = tgui_input_text(user, "Write something", "Newspaper") - if (!s) - return - if(!user.can_perform_action(src)) - return - scribble_page = curr_page - scribble = s - attack_self(user) - add_fingerprint(user) - else - return ..() + return TRUE + SStgui.update_uis(src) + playsound(src, SFX_PAGE_TURN, 50, TRUE) + return TRUE + +/obj/item/newspaper/ui_static_data(mob/user) + var/list/data = list() + data["channels"] = list() + for(var/datum/feed_channel/news_channels as anything in news_content) + data["channels"] += list(list( + "name" = news_channels.channel_name, + "page_number" = news_content.Find(news_channels), + )) + return data + +/obj/item/newspaper/ui_data(mob/user) + var/list/data = list() + data["current_page"] = current_page + data["scribble_message"] = (scribble_page == current_page) ? scribble_text : null + if(saved_wanted_icon) + user << browse_rsc(saved_wanted_icon, "wanted_photo.png") + data["wanted_criminal"] = saved_wanted_criminal + data["wanted_body"] = saved_wanted_body + data["wanted_photo"] = (saved_wanted_icon ? "wanted_photo.png" : null) + + var/list/channel_data = list() + if(!current_page || (current_page == news_content.len + 1)) + channel_data["channel_name"] = null + channel_data["author_name"] = null + channel_data["is_censored"] = null + channel_data["channel_messages"] = list() + data["channel_data"] = list(channel_data) + return data + var/datum/feed_channel/current_channel = news_content[current_page] + if(istype(current_channel)) + channel_data["channel_name"] = current_channel.channel_name + channel_data["author_name"] = current_channel.return_author(censored_check(current_channel.author_censor_time)) + channel_data["is_censored"] = censored_check(current_channel.D_class_censor_time) + channel_data["channel_messages"] = list() + for(var/datum/feed_message/feed_messages as anything in current_channel.messages) + if(feed_messages.creation_time > creation_time) + data["channel_has_messages"] = FALSE + break + data["channel_has_messages"] = TRUE + var/has_image = FALSE + if(feed_messages.img) + has_image = TRUE + user << browse_rsc(feed_messages.img, "tmp_photo[feed_messages.message_ID].png") + channel_data["channel_messages"] += list(list( + "message" = "-[feed_messages.return_body(censored_check(feed_messages.body_censor_time))]", + "photo" = (has_image ? "tmp_photo[feed_messages.message_ID].png" : null), + "author" = feed_messages.return_author(censored_check(feed_messages.author_censor_time)), + )) + data["channel_data"] = list(channel_data) + return data diff --git a/code/modules/admin/verbs/admin_newscaster.dm b/code/modules/admin/verbs/admin_newscaster.dm index 0a25ebaca65..2f6870394c2 100644 --- a/code/modules/admin/verbs/admin_newscaster.dm +++ b/code/modules/admin/verbs/admin_newscaster.dm @@ -78,14 +78,15 @@ data["crime_description"] = crime_description var/list/wanted_info = list() if(GLOB.news_network.wanted_issue) - if(GLOB.news_network.wanted_issue.img) + var/has_wanted_issue = !isnull(GLOB.news_network.wanted_issue.img) + if(has_wanted_issue) user << browse_rsc(GLOB.news_network.wanted_issue.img, "wanted_photo.png") wanted_info = list(list( "active" = GLOB.news_network.wanted_issue.active, "criminal" = GLOB.news_network.wanted_issue.criminal, "crime" = GLOB.news_network.wanted_issue.body, "author" = GLOB.news_network.wanted_issue.scanned_user, - "image" = "wanted_photo.png" + "image" = (has_wanted_issue ? "wanted_photo.png" : null) )) //Code breaking down the channels that have been made on-station thus far. ha @@ -320,6 +321,7 @@ new_feed_comment.body = comment_text new_feed_comment.time_stamp = station_time_timestamp() current_message.comments += new_feed_comment + GLOB.news_network.last_action ++ usr.log_message("(as an admin) commented on message [current_message.return_body(-1)] -- [current_message.body]", LOG_COMMENT) creating_comment = FALSE diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 052d10e494f..096afab907d 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -496,10 +496,6 @@ if(incapacitated()) return - if (href_list["mach_close"]) - var/t1 = "window=[href_list["mach_close"]]" - unset_machine() - src << browse(null, t1) if (href_list["switchcamera"]) switchCamera(locate(href_list["switchcamera"]) in GLOB.cameranet.cameras) if (href_list["showalerts"]) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 817e9ce9474..b9a82194609 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -916,18 +916,6 @@ set hidden = TRUE set category = null return -/** - * Topic call back for any mob - * - * * Unset machines if "mach_close" sent - * * refresh the inventory of machines in range if "refresh" sent - * * handles the strip panel equip and unequip as well if "item" sent - */ -/mob/Topic(href, href_list) - if(href_list["mach_close"]) - var/t1 = "window=[href_list["mach_close"]]" - unset_machine() - src << browse(null, t1) /** * Controls if a mouse drop succeeds (return null if it doesnt) diff --git a/tgui/packages/tgui/interfaces/Newscaster.jsx b/tgui/packages/tgui/interfaces/Newscaster.jsx index a15fc8ef9ba..431e7d606e6 100644 --- a/tgui/packages/tgui/interfaces/Newscaster.jsx +++ b/tgui/packages/tgui/interfaces/Newscaster.jsx @@ -6,7 +6,6 @@ */ import { decodeHtmlEntities } from 'common/string'; -import { marked } from 'marked'; import { useState } from 'react'; import { useBackend, useSharedState } from '../backend'; @@ -23,7 +22,7 @@ import { Tabs, TextArea, } from '../components'; -import { sanitizeText } from '../sanitize'; +import { processedText } from '../process'; import { BountyBoardContent } from './BountyBoard'; import { UserDetails } from './Vending'; @@ -90,8 +89,8 @@ const NewscasterChannelCreation = (props) => { Enter channel name here: @@ -181,9 +183,9 @@ const NewscasterCommentCreation = (props) => { Enter comment: @@ -236,7 +239,7 @@ const NewscasterWantedScreen = (props) => { return null; } return ( - + {wanted.map((activeWanted) => ( <> @@ -246,22 +249,27 @@ const NewscasterWantedScreen = (props) => { ? 'Active Wanted Issue:' : 'Dismissed Wanted Issue:'}
@@ -293,22 +303,25 @@ const NewscasterWantedScreen = (props) => { icon="camera" selected={photo_data} disabled={!security_mode} - content={photo_data ? 'Remove photo' : 'Attach photo'} onClick={() => act('togglePhoto')} - /> + > + {photo_data ? 'Remove photo' : 'Attach photo'} +
) : ( @@ -392,28 +405,29 @@ const NewscasterChannelBox = (props) => { {!!admin_mode && ( )}
@@ -493,20 +510,6 @@ const NewscasterChannelSelector = (props) => { ); }; -const processedText = (value) => { - const textHtml = { - __html: sanitizeText( - marked(value, { - breaks: true, - smartypants: true, - smartLists: true, - baseUrl: 'thisshouldbreakhttp', - }), - ), - }; - return textHtml; -}; - /** This is where the channels comments get spangled out (tm) */ const NewscasterChannelMessages = (props) => { const { act, data } = useBackend(); diff --git a/tgui/packages/tgui/interfaces/Newspaper.tsx b/tgui/packages/tgui/interfaces/Newspaper.tsx new file mode 100644 index 00000000000..2629a398c95 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Newspaper.tsx @@ -0,0 +1,166 @@ +import { BooleanLike } from '../../common/react'; +import { useBackend } from '../backend'; +import { Box, Button, Divider, Image, Section } from '../components'; +import { Window } from '../layouts'; +import { processedText } from '../process'; + +type Data = { + current_page: number; + scribble_message: string | null; + channel_has_messages: BooleanLike; + channels: ChannelNames[]; + channel_data: ChannelData[]; + wanted_criminal: string | null; + wanted_body: string | null; + wanted_photo: string | null; +}; + +type ChannelNames = { + name: string | null; + page_number: number; +}; + +type ChannelData = { + channel_name: string | null; + author_name: string | null; + is_censored: BooleanLike; + channel_messages: ChannelMessages[]; +}; + +type ChannelMessages = { + message: string | null; + photo: string | null; + author: string | null; +}; + +export const Newspaper = (props) => { + const { act, data } = useBackend(); + const { channels = [], current_page, scribble_message } = data; + + return ( + + + {current_page === channels.length + 1 ? ( + + ) : current_page ? ( + + ) : ( + + )} + {!!scribble_message && ( + + {scribble_message} + + )} +
+ + +
+
+
+ ); +}; + +const NewspaperIntro = (props) => { + const { act, data } = useBackend(); + const { channels = [], wanted_criminal = [] } = data; + + return ( +
+ + The Griffon + + + For use on Space Facilities only! + + Table of Contents: + {channels.map((channel) => ( + + Page {channel.page_number || 0}: {channel.name} + + ))} + {!!wanted_criminal && ( + Last Page: Important Security Announcement + )} +
+ ); +}; + +const NewspaperChannel = (props) => { + const { act, data } = useBackend(); + const { channel_has_messages, channel_data = [] } = data; + + return ( +
+ {channel_data.map((individual_channel) => ( + + + {individual_channel.channel_name} + + + Channel made by: {individual_channel.author_name} + + {channel_has_messages ? ( + <> + {individual_channel.channel_messages.map((message) => ( + <> + + + {!!message.photo && } + Written by: {message.author} + + + + ))} + + ) : ( + 'No feed stories stem from this channel...' + )} + + ))} +
+ ); +}; + +const NewspaperEnding = (props) => { + const { act, data } = useBackend(); + const { wanted_criminal, wanted_body, wanted_photo } = data; + + return ( +
+ {wanted_criminal ? ( + <> + + Wanted Issue + + Criminal Name: {wanted_criminal} + Description: {wanted_body} + {!!wanted_photo && } + + ) : ( + 'Apart from some uninteresting classified ads, theres nothing in this page...' + )} +
+ ); +}; diff --git a/tgui/packages/tgui/process.ts b/tgui/packages/tgui/process.ts new file mode 100644 index 00000000000..62b992d16fb --- /dev/null +++ b/tgui/packages/tgui/process.ts @@ -0,0 +1,17 @@ +import { marked } from 'marked'; + +import { sanitizeText } from './sanitize'; + +export const processedText = (value) => { + const textHtml = { + __html: sanitizeText( + marked(value, { + breaks: true, + smartypants: true, + smartLists: true, + baseUrl: 'thisshouldbreakhttp', + }), + ), + }; + return textHtml; +};